More JSON schema improvements (#729)
* Add support for constant values * Add contains check for arrays * Add tests * Simplify getValidValueOrDefault testing
This commit is contained in:
parent
abfa0362dd
commit
587822c16e
@ -326,6 +326,11 @@ class JsonSchemaValidator {
|
|||||||
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;
|
||||||
|
if (typeof schemaConst !== 'undefined' && !this.valuesAreEqual(value, schemaConst)) {
|
||||||
|
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);
|
||||||
@ -414,6 +419,8 @@ 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);
|
||||||
|
|
||||||
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);
|
||||||
@ -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) {
|
validateObject(value, schema, info) {
|
||||||
const properties = new Set(Object.getOwnPropertyNames(value));
|
const properties = new Set(Object.getOwnPropertyNames(value));
|
||||||
|
|
||||||
|
@ -185,6 +185,212 @@ function testValidate2() {
|
|||||||
inputs: [
|
inputs: [
|
||||||
{expected: false, value: ''}
|
{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,8 +413,10 @@ function testValidate2() {
|
|||||||
|
|
||||||
|
|
||||||
function testGetValidValueOrDefault1() {
|
function testGetValidValueOrDefault1() {
|
||||||
|
const data = [
|
||||||
// Test value defaulting on objects with additionalProperties=false
|
// Test value defaulting on objects with additionalProperties=false
|
||||||
const schema = {
|
{
|
||||||
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['test'],
|
required: ['test'],
|
||||||
properties: {
|
properties: {
|
||||||
@ -218,9 +426,8 @@ function testGetValidValueOrDefault1() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
};
|
},
|
||||||
|
inputs: [
|
||||||
const testData = [
|
|
||||||
[
|
[
|
||||||
void 0,
|
void 0,
|
||||||
{test: 'default'}
|
{test: 'default'}
|
||||||
@ -257,17 +464,12 @@ function testGetValidValueOrDefault1() {
|
|||||||
{test: 'value', test2: 'value2'},
|
{test: 'value', test2: 'value2'},
|
||||||
{test: 'value'}
|
{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
|
// Test value defaulting on objects with additionalProperties=true
|
||||||
const schema = {
|
{
|
||||||
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['test'],
|
required: ['test'],
|
||||||
properties: {
|
properties: {
|
||||||
@ -277,9 +479,8 @@ function testGetValidValueOrDefault2() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
additionalProperties: true
|
additionalProperties: true
|
||||||
};
|
},
|
||||||
|
inputs: [
|
||||||
const testData = [
|
|
||||||
[
|
[
|
||||||
{},
|
{},
|
||||||
{test: 'default'}
|
{test: 'default'}
|
||||||
@ -296,17 +497,12 @@ function testGetValidValueOrDefault2() {
|
|||||||
{test: 'value', test2: 'value2'},
|
{test: 'value', 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}
|
// Test value defaulting on objects with additionalProperties={schema}
|
||||||
const schema = {
|
{
|
||||||
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['test'],
|
required: ['test'],
|
||||||
properties: {
|
properties: {
|
||||||
@ -319,9 +515,8 @@ function testGetValidValueOrDefault3() {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
default: 10
|
default: 10
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
inputs: [
|
||||||
const testData = [
|
|
||||||
[
|
[
|
||||||
{},
|
{},
|
||||||
{test: 'default'}
|
{test: 'default'}
|
||||||
@ -354,12 +549,16 @@ function testGetValidValueOrDefault3() {
|
|||||||
{test: 'value', test2: 2, test3: void 0},
|
{test: 'value', test2: 2, test3: void 0},
|
||||||
{test: 'value', test2: 2, test3: 10}
|
{test: 'value', test2: 2, test3: 10}
|
||||||
]
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const [value, expected] of testData) {
|
for (const {schema, inputs} of data) {
|
||||||
|
for (const [value, expected] of inputs) {
|
||||||
const actual = JsonSchema.getValidValueOrDefault(schema, value);
|
const actual = JsonSchema.getValidValueOrDefault(schema, value);
|
||||||
vm.assert.deepStrictEqual(actual, expected);
|
vm.assert.deepStrictEqual(actual, expected);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -367,8 +566,6 @@ function main() {
|
|||||||
testValidate1();
|
testValidate1();
|
||||||
testValidate2();
|
testValidate2();
|
||||||
testGetValidValueOrDefault1();
|
testGetValidValueOrDefault1();
|
||||||
testGetValidValueOrDefault2();
|
|
||||||
testGetValidValueOrDefault3();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user