Files
DefinitelyTyped/types/jsen/jsen-tests.ts
2017-03-24 14:27:52 -07:00

3481 lines
97 KiB
TypeScript

import jsen = require("jsen");
import JsenSettings = jsen.JsenSettings;
// any
{
// passes validation on any type
{
const schema = { type: "any" };
const validate = jsen(schema);
console.assert(validate(null));
console.assert(validate(undefined));
console.assert(validate(0));
console.assert(validate(''));
console.assert(validate(Math.PI));
console.assert(validate('abc'));
console.assert(validate(77));
console.assert(validate(false));
console.assert(validate(true));
console.assert(validate({}));
console.assert(validate([]));
}
}
// type: array
{
// required
{
const schema = { type: 'array' };
const validate = jsen(schema);
console.assert(!validate());
console.assert(!validate(null));
console.assert(validate([]));
}
// nullable
{
const schema = { type: ['array', 'null'] };
const validate = jsen(schema);
console.assert(!validate(undefined));
console.assert(validate(null));
console.assert(validate([]));
}
// type
{
const schema = { type: 'array' };
const validate = jsen(schema);
console.assert(!validate('123'));
console.assert(!validate(false));
console.assert(!validate({}));
console.assert(!validate(Math.PI));
console.assert(validate([]));
}
// minItems
{
const schema = { type: 'array', minItems: 3 };
const validate = jsen(schema);
console.assert(!validate([]));
console.assert(!validate([1, 2]));
console.assert(validate([1, 2, 3]));
console.assert(validate([1, 2, 3, 4]));
}
// maxItems
{
const schema = { type: 'array', maxItems: 3 };
const validate = jsen(schema);
console.assert(!validate([1, 2, 3, 4]));
console.assert(validate([]));
console.assert(validate([1, 2, 3]));
}
// items: object
{
let schema: any = { type: 'array', items: { type: 'string' } };
let validate = jsen(schema);
console.assert(!validate(null));
console.assert(!validate([1]));
console.assert(!validate(['a', false, 'b']));
console.assert(!validate(['a', 'b', 1]));
console.assert(validate([]));
console.assert(validate(['a']));
console.assert(validate(['a', 'b', 'c']));
schema = {
type: 'array',
items: {
type: 'object',
properties: {
strProp: { type: 'string' },
boolProp: { type: 'boolean' }
},
required: ['strProp']
}
};
validate = jsen(schema);
console.assert(!validate([123]));
console.assert(!validate([{}]));
console.assert(!validate([{
strProp: 'value',
boolProp: 123
}]));
console.assert(validate([{
strProp: 'value',
boolProp: false
}]));
}
// items: array
{
const schema = {
type: 'array',
items: [
{ type: 'string' },
{ type: 'number' }
]
};
const validate = jsen(schema);
console.assert(!validate([1]));
console.assert(!validate([1, 'a']));
console.assert(validate([]));
console.assert(validate(['a']));
console.assert(validate(['a', 1]));
console.assert(validate(['a', 1, null, 'b', 2]));
}
// additionalItems: boolean
{
let schema: any = { type: 'array', additionalItems: false };
let validate = jsen(schema);
console.assert(validate([]));
console.assert(validate([1]));
console.assert(validate([1, 'a', true]));
schema.items = { type: 'number' };
validate = jsen(schema);
console.assert(!validate(['a']));
console.assert(validate([]));
console.assert(validate([1]));
console.assert(validate([1, 2, 3]));
schema.items = [
{ type: 'string' },
{ type: 'number' }
];
validate = jsen(schema);
console.assert(!validate(['a', 1, 2]));
console.assert(validate([]));
console.assert(validate(['a']));
console.assert(validate(['a', 1]));
}
// additionalItems: object
{
// when `items` is an object schema, `additionalItems`
// is ignored and must not validate against
let schema: any = {
type: 'array',
items: {
type: 'string'
},
additionalItems: {
type: 'number'
}
};
let validate = jsen(schema);
console.assert(!validate(['abc', 'def', 123]));
// same as above description - only strings are valid
console.assert(validate(['abc', 'def']));
// when `items` is an array, any other positional
// data item must validate against `additionalItems`
schema.items = [
{ type: 'string' },
{ type: 'boolean' }
];
validate = jsen(schema);
console.assert(!validate(['abc', false, 'def']));
console.assert(validate(['abc', false]));
console.assert(validate(['abc', false, 123]));
console.assert(validate(['abc', false, 123, Math.PI]));
// when `additionalItems` is an empty object, anything is valid
schema.additionalItems = {};
validate = jsen(schema);
console.assert(validate(['abc', false, 'def', 123, {}, null]));
}
// uniqueItems
{
let schema = {
type: 'array',
items: { type: 'number' },
uniqueItems: false
};
let validate = jsen(schema);
console.assert(validate([1, 2, 1]));
schema.uniqueItems = true;
validate = jsen(schema);
console.assert(!validate([1, 2, 1]));
console.assert(validate([1, 2, 3]));
}
}
// type: boolean
{
// required
{
const schema = { type: 'boolean' };
const validate = jsen(schema);
console.assert(!validate());
console.assert(!validate(null));
console.assert(validate(false));
console.assert(validate(true));
}
// nullable
{
const schema = { type: ['boolean', 'null'] };
const validate = jsen(schema);
console.assert(!validate(undefined));
console.assert(validate(true));
console.assert(validate(false));
console.assert(validate(null));
}
// type
{
const schema = { type: 'boolean' };
const validate = jsen(schema);
console.assert(!validate('123'));
console.assert(!validate([]));
console.assert(!validate({}));
console.assert(!validate(Math.PI));
console.assert(validate(true));
console.assert(validate(false));
}
}
// build
{
// validator function has a build property
{
const validate = jsen({});
console.assert(typeof validate.build === 'function');
console.assert(validate.build.length === 2);
}
// returns default value from schema if initial undefined
{
const validate = jsen({ type: 'string', default: 'abc' });
console.assert(validate.build() === 'abc');
console.assert(validate.build(undefined) === 'abc');
}
// does not modify initial defined value
{
const validate = jsen({ type: 'string', default: 'abc' });
const initials = [
null,
'',
'string value',
true,
false,
123,
Math.PI
];
let obj: any;
initials.forEach((initial) => {
obj = validate.build(initial);
console.assert(obj === initial);
});
}
// returns a copy of initial defined value
{
const validate = jsen({ type: 'string', default: 'abc' });
const initials = [
{},
[],
new Date()
];
let obj: any;
initials.forEach((initial) => {
obj = validate.build(initial);
console.assert(obj !== initial);
console.assert(JSON.stringify(obj) === JSON.stringify(initial));
});
}
// returns initial if no default in schema
{
const validate = jsen({}),
initials = [
undefined,
null,
'',
'string value',
true,
false,
123,
Math.PI,
{},
[],
new Date()
];
let obj: any;
initials.forEach((initial) => {
obj = validate.build(initial);
console.assert(JSON.stringify(obj) === JSON.stringify(initial));
});
}
// returns the default value specified in schema
{
const schemas = [
{ default: null },
{ default: undefined },
{ default: '' },
{ default: 'abc' },
{ default: true },
{ default: false },
{ default: 123 },
{ default: Math.PI }
];
schemas.forEach((schema) => {
let validate = jsen(schema);
console.assert(validate.build() === schema.default);
});
}
// returns a copy of a default object or array in schema
{
const schemas = [
{ default: { a: { b: 123 } } },
{ default: [[1, 2, 3], { a: { b: 123 } }] },
{ default: /\d+/ },
{ default: new Date('05/14/2015') }
];
schemas.forEach((schema) => {
let validate = jsen(schema);
let def = validate.build();
console.assert(def !== schema.default);
console.assert(JSON.stringify(def) === JSON.stringify(schema.default));
});
}
// recursively collects default values
{
const schema: any = {
type: 'object',
default: {},
properties: {
a: {
type: 'array',
default: [],
items: {
type: 'string'
}
},
b: {
type: 'array',
default: [],
items: {
type: 'string',
default: 'abc'
}
},
c: {
type: 'object',
default: {},
properties: {
d: {
type: 'boolean',
default: false
},
e: {
type: 'date',
default: new Date('05/14/2015')
},
f: {
type: 'array',
default: [{}, {}],
items: [{
type: 'object',
properties: {
g: {
type: 'string',
default: 'yes'
}
}
}, {
type: 'object',
properties: {
g: {
type: 'integer',
default: 0
}
}
}, {
type: 'object',
properties: {
g: {
type: 'boolean',
default: true
}
}
}]
},
h: {
type: 'array',
default: [{}, {}],
items: {
type: 'object',
properties: {
i: {
type: 'object',
default: { foo: 'bar' }
}
}
}
},
i: {
type: 'object',
default: null,
properties: {
j: {
type: 'string',
default: 'baz'
}
}
}
}
},
j: {
type: 'object',
properties: {
k: {
type: 'boolean',
default: false
}
}
}
}
};
const expected: any = {
a: [],
b: [],
c: {
d: false,
e: new Date('05/14/2015'),
f: [
{ g: 'yes' },
{ g: 0 }
],
h: [
{ i: { foo: 'bar' } },
{ i: { foo: 'bar' } }
],
i: null
}
};
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
}
// merges default values with the initial values
{
const schemas = [
{
properties: {
foo: { default: 'bar' }
}
},
{
properties: {
foo: { default: 'bar' }
}
},
{
properties: {
foo: { default: 'bar' }
}
},
{
items: {
properties: {
foo: { default: 'bar' }
}
}
},
{
items: {
properties: {
foo: { default: 'bar' }
}
}
},
{
items: [
{ default: 'foo' },
{ default: 'bar' },
{ default: 'baz' }
]
},
{
items: [
{ default: 'foo' },
{ default: 'bar' },
{ default: 'baz' }
]
}
];
const defaults = [
{},
{ foo: 'baz' },
{ x: 'yz' },
[],
[{}],
[],
[null, {}, undefined, false]
];
const expected = [
{ foo: 'bar' },
{ foo: 'baz' },
{ foo: 'bar', x: 'yz' },
[],
[{ foo: 'bar' }],
['foo', 'bar', 'baz'],
[null, {}, 'baz', false]
];
schemas.forEach((schema, index) => {
let validate = jsen(schema);
console.assert(JSON.stringify(validate.build(defaults[index])) === JSON.stringify(expected[index]));
});
}
// $ref
{
const schema = {
definitions: {
positiveInteger: {
type: 'integer',
minimum: 1,
default: 7
}
},
$ref: '#definitions/positiveInteger'
};
const validate = jsen(schema);
console.assert(validate.build() === 7);
}
// object
{
// clones default object and subproperties recursively
{
const schema = {
default: {},
properties: {
foo: {
type: 'string',
default: 'bar'
}
}
};
const expected = { foo: 'bar' };
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
}
// does not recursively assign defaults of children if parent default is not an object
{
const schema: any = {
default: [],
properties: {
foo: {
type: 'string',
default: 'bar'
}
}
};
const expected: any = [];
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
}
}
// array
{
// does not add new elements to default array: items schema is an object
{
const schema: any = {
default: [],
items: {
type: 'string',
default: 'bar'
}
};
const expected: any = [];
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
}
// adds new elements to default array: items schema is an array
{
const schema: any = {
default: [],
items: [{
type: 'string',
default: 'bar'
}]
};
const expected = ['bar'];
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
}
// adds default values to already existing child items of compatible type only: items schema is an object
{
const schema: any = {
default: [{}, null, []],
items: {
properties: {
foo: {
type: 'string',
default: 'bar'
}
}
}
};
const expected: any = [{ foo: 'bar' }, null, []];
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
}
// adds default values to already existing child items of compatible type only: items schema is an array
{
const schema: any = {
default: [{}, null, {}, undefined],
items: [
{
properties: {
foo: {
type: 'string',
default: 'bar'
}
}
},
{ default: 'abc' },
{ default: 123 },
{ default: 123 }
]
};
const expected = [{ foo: 'bar' }, null, {}, 123];
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
}
// does not assign default child items if parent default is not an array
{
const schema: any = {
default: 'foobar',
items: {
type: 'string',
default: 'bar'
}
};
const expected = 'foobar';
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
}
}
// option: copy
{
// returns deep copy of the initial object by default
{
const schema = {};
const initial = {};
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build(initial)) === JSON.stringify(initial));
console.assert(validate.build(initial) !== initial);
}
// modifies the initial object when copy = false
{
const schema = {
properties: {
a: { default: 'foo' },
b: {
items: {
properties: {
c: { default: 'bar' },
d: { default: 'baz' }
}
}
}
}
};
const initial = { b: [{ d: 'xyz' }] };
const expected = { a: 'foo', b: [{ c: 'bar', d: 'xyz' }] };
const validate = jsen(schema);
const actual = validate.build(initial, { copy: false });
console.assert(JSON.stringify(actual) === JSON.stringify(expected));
console.assert(actual === initial);
}
}
// option: additionalProperties
{
// includes by default
{
const schema = {
properties: {
foo: {}
}
};
const initial = { foo: 1, bar: 2 };
const expected = { foo: 1, bar: 2 };
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build(initial)) === JSON.stringify(expected));
}
// excludes when schema.additionalProperties = false
{
const schema = {
additionalProperties: false,
properties: {
foo: {}
}
};
const initial = { foo: 1, bar: 2 };
const expected = { foo: 1 };
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build(initial)) === JSON.stringify(expected));
}
// removes from the initial object when schema.additionalProperties = false and options.copy = false
{
const schema = {
additionalProperties: false,
properties: {
foo: {}
}
};
const initial = { foo: 1, bar: 2 };
const expected = { foo: 1 };
const validate = jsen(schema);
const actual = validate.build(initial, { copy: false });
console.assert(actual === initial);
console.assert(JSON.stringify(actual) === JSON.stringify(expected));
}
// removes from the initial object when options.additionalProperties = false and options.copy = false
{
const schema = {
properties: {
foo: {}
}
};
const initial = { foo: 1, bar: 2 };
const expected = { foo: 1 };
const validate = jsen(schema);
const actual = validate.build(initial, {
copy: false,
additionalProperties: false
});
console.assert(actual === initial);
console.assert(JSON.stringify(actual) === JSON.stringify(expected));
}
// schema.additionalProperties takes precedence
{
const schema = {
additionalProperties: true,
properties: {
foo: {}
}
};
const initial = { foo: 1, bar: 2 };
const expected = { foo: 1, bar: 2 };
const validate = jsen(schema);
console.assert(JSON.stringify(validate.build(initial, { additionalProperties: false })) === JSON.stringify(expected));
}
}
}
// clone
{
const clone = jsen.clone;
// string
{
console.assert(clone('abc') === 'abc');
}
// number
{
console.assert(clone(123) === 123);
}
// integer
{
console.assert(clone(Math.PI) === Math.PI);
}
// boolean
{
console.assert(clone(false) === false);
}
// function
{
const func = () => {
};
console.assert(clone(func) === func);
}
// regexp
{
const regex = /a/gim;
console.assert(clone(regex) !== regex);
console.assert(clone(regex).toString() === regex.toString());
}
// date
{
const today = new Date('05/14/2015');
console.assert(clone(today) !== today);
console.assert(clone(today).toJSON() === today.toJSON());
}
// null
{
console.assert(clone(null) === null);
}
// undefined
{
console.assert(clone(undefined) === undefined);
}
// object
{
const obj = { a: 1, b: 'a', c: false, d: { e: Math.PI, f: [1, 2, 3] } };
console.assert(clone(obj) !== obj);
console.assert(JSON.stringify(clone(obj)) === JSON.stringify(obj));
}
// array
{
const arr = [1, 'a', false, { d: [1, 2, 3] }];
console.assert(clone(arr) !== arr);
console.assert(JSON.stringify(clone(arr)) === JSON.stringify(arr));
}
}
// type: date
{
// required
{
const schema = { type: 'date' };
const validate = jsen(schema);
console.assert(!validate());
console.assert(!validate(null));
console.assert(validate(new Date()));
}
// nullable
{
const schema = { type: ['date', 'null'] };
const validate = jsen(schema);
console.assert(!validate(undefined));
console.assert(validate(new Date()));
console.assert(validate(null));
}
// type
{
const schema = { type: 'date' };
const validate = jsen(schema);
console.assert(!validate('123'));
console.assert(!validate([]));
console.assert(!validate({}));
console.assert(!validate(Math.PI));
console.assert(validate(new Date()));
}
}
// equal
{
const equal = jsen.equal;
// string
{
console.assert(equal('a', 'a'));
console.assert(!equal('a', 'b'));
}
// number
{
console.assert(equal(123, 123));
console.assert(equal(Math.PI, Math.PI));
console.assert(!equal(Math.PI, Math.E));
}
// boolean
{
console.assert(equal(true, true));
console.assert(!equal(true, false));
}
// null
{
console.assert(equal(null, null));
console.assert(!equal(null, undefined));
}
// undefined
{
console.assert(equal(undefined, undefined));
console.assert(!equal(null, undefined));
}
// function
{
const f1 = () => {
};
const f2 = () => {
};
const f3 = f1;
// two functions are only equal if they
// reference the same function object
console.assert(equal(f1, f3));
console.assert(!equal(f1, f2));
}
// array
{
const f = () => {
};
const obj1 = { a: 123, b: 'abc', c: f };
const obj2 = { a: 123, b: 'abc', c: f };
const arr1 = [1, 'a', f, obj1];
const arr2 = [1, 'a', f, obj1];
const arr3 = [1, 'a', f];
const arr4 = [1, 'a', f, obj2];
console.assert(equal(arr1, arr2));
console.assert(equal(arr1, arr4));
console.assert(!equal(arr1, arr3));
}
// object
{
const a = { a: 123, b: ['abc'], c: {} };
const b = { b: ['abc'], c: {}, a: 123 };
const c = a;
const d: any = { a: 123, b: ['abc'], c: { d: undefined } };
console.assert(equal(a, b));
console.assert(equal(a, c));
console.assert(!equal(a, d));
}
// regexp
{
const a = /a+/gim;
const b = new RegExp('a+', 'gim');
const c = /a/gim;
const d = a;
console.assert(equal(a, b));
console.assert(equal(a, d));
console.assert(!equal(a, c));
}
}
// error
{
// errors is empty array before validation
{
const schema = { type: 'number' };
const validate = jsen(schema);
console.assert(Array.isArray(validate.errors));
console.assert(validate.errors.length === 0);
}
// no errors on successful validation
{
const schema = { type: 'number' };
const validate = jsen(schema);
const valid = validate(123);
console.assert(valid);
console.assert(Array.isArray(validate.errors));
console.assert(validate.errors.length === 0);
}
// has errors when validation unsuccessful
{
const schema = { type: 'number' };
const validate = jsen(schema);
const valid = validate('123');
console.assert(!valid);
console.assert(Array.isArray(validate.errors));
console.assert(validate.errors.length === 1);
}
// clears errors on successive validation calls
{
const schema = { type: 'number' };
const validate = jsen(schema);
validate('123');
console.assert(Array.isArray(validate.errors));
console.assert(validate.errors.length === 1);
validate(123);
console.assert(Array.isArray(validate.errors));
console.assert(validate.errors.length === 0);
validate('123');
console.assert(Array.isArray(validate.errors));
console.assert(validate.errors.length === 1);
}
// two successive runs return different arrays
{
const schema = { type: 'number' };
const validate = jsen(schema);
let previous: any;
validate('123');
console.assert(validate.errors.length === 1);
previous = validate.errors;
validate('123');
console.assert(validate.errors.length === 1);
console.assert(validate.errors !== previous);
console.assert(JSON.stringify(validate.errors) === JSON.stringify(previous));
}
// error object
{
const schemas = [
{
type: 'number'
},
{
type: 'object',
properties: {
a: {
type: 'string'
}
}
},
{
type: 'array',
uniqueItems: true
},
{
type: 'array',
items: {
maximum: 10
}
},
{
type: 'object',
properties: {
a: {
type: 'array',
items: [{
type: 'object',
properties: {
b: {
multipleOf: 7
}
}
}]
}
}
},
{
allOf: [
{ minimum: 5 },
{ maximum: 10 }
]
},
{
type: 'object',
properties: {
a: {
anyOf: [
{ type: 'string' },
{ type: 'number' }
]
}
}
},
{
type: 'array',
items: [{
type: 'object',
properties: {
a: {
oneOf: [
{ type: 'boolean' },
{ type: 'null' }
]
}
}
}]
},
{
type: 'object',
properties: {
a: {
not: {
type: 'string'
}
}
}
},
{
definitions: {
positiveInteger: {
type: 'integer',
minimum: 0,
exclusiveMinimum: true
}
},
type: 'object',
properties: {
a: {
type: 'object',
properties: {
b: {
type: 'object',
properties: {
c: {
$ref: '#/definitions/positiveInteger'
}
}
}
}
}
}
},
{
type: 'object',
required: ['a', 'b']
},
{
type: 'object',
dependencies: {
a: {
required: ['b']
}
}
},
{
type: 'object',
dependencies: {
a: ['b']
}
}
];
const data = [
'123',
{ a: 123 },
[7, 11, 7],
[10, 11, 9],
{ a: [{ b: 8 }] },
12,
{ a: false },
[{ a: 123 }],
{ a: 'abc' },
{ a: { b: { c: 0 } } },
{},
{ a: 123 },
{ a: 123 }
];
// property: path
{
const expectedPaths = [
[''],
['a'],
[''],
['1'],
['a.0.b'],
[''],
['a', 'a'],
['0.a', '0.a', '0.a'],
['a'],
['a.b.c'],
['a'],
['b'],
['b']
];
schemas.forEach((schema, index) => {
let validate = jsen(schema);
let valid = validate(data[index]);
console.assert(!valid);
expectedPaths[index].forEach((path, pindex) => {
try {
console.assert(validate.errors[pindex].path === path);
}
catch (e) {
// console.log(index);
// console.log(validate.errors);
throw e;
}
});
});
}
// property: keyword
{
const expectedKeywords = [
['type'],
['type'],
['uniqueItems'],
['maximum'],
['multipleOf'],
['maximum'],
['type', 'type', 'anyOf'],
['type', 'type', 'oneOf'],
['not'],
['exclusiveMinimum'],
['required'],
['required'],
['dependencies']
];
schemas.forEach((schema, index) => {
let validate = jsen(schema);
let valid = validate(data[index]);
console.assert(!valid);
expectedKeywords[index].forEach((keyword, kindex) => {
try {
console.assert(validate.errors[kindex].keyword === keyword);
}
catch (e) {
// console.log(index);
// console.log(validate.errors);
throw e;
}
});
});
}
// adds required property name to path
{
let schema: any = { type: 'object', required: ['a'] };
let validate = jsen(schema);
let valid = validate({});
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].path === 'a');
console.assert(validate.errors[0].keyword === 'required');
schema = {
type: 'object',
properties: {
a: {
type: 'array',
items: {
type: 'object',
required: ['b']
}
}
}
};
validate = jsen(schema);
valid = validate({ a: [{}] });
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].path === 'a.0.b');
console.assert(validate.errors[0].keyword === 'required');
}
// adds required dependency property to path
{
let schema: any = {
type: 'object',
dependencies: {
a: ['b']
}
};
let validate = jsen(schema);
let valid = validate({ a: 123 });
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].path === 'b');
console.assert(validate.errors[0].keyword === 'dependencies');
schema = {
type: 'object',
properties: {
a: {
type: 'array',
items: {
type: 'object',
dependencies: {
a: ['b']
}
}
}
}
};
validate = jsen(schema);
valid = validate({ a: [{ a: 123 }] });
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].path === 'a.0.b');
console.assert(validate.errors[0].keyword === 'dependencies');
}
}
// multiple errors
{
const schema = {
definitions: {
array: {
maxItems: 1
}
},
type: 'object',
properties: {
a: {
anyOf: [
{ items: { type: 'integer' } },
{ $ref: '#/definitions/array' },
{ items: [{ maximum: 3 }] }
]
}
}
};
const data = { a: [Math.PI, Math.E] };
const validate = jsen(schema);
// returns multiple errors
{
const valid = validate(data);
// console.log(validate.errors);
console.assert(!valid);
console.assert(validate.errors.length === 5);
}
}
// custom errors
{
const schemas = [
{
type: 'string',
invalidMessage: 'string is invalid',
requiredMessage: 'string is required'
},
{
type: 'object',
required: ['a'],
properties: {
a: {
invalidMessage: 'a is invalid',
requiredMessage: 'a is required'
}
}
},
{
type: 'array',
items: {
type: 'object',
properties: {
a: {
type: 'object',
properties: {
b: {
invalidMessage: 'b is invalid',
requiredMessage: 'b is required'
}
},
required: ['b']
}
}
}
},
{
type: 'object',
properties: {
a: {
type: 'object',
properties: {
c: {
type: 'string',
invalidMessage: 'c is invalid',
requiredMessage: 'c is required'
}
}
}
}
}
];
const data = [
undefined,
{},
[{ a: {} }],
{ a: { c: 123 } }
];
const expectedMessages = [
'string is invalid',
'a is required',
'b is required',
'c is invalid'
];
schemas.forEach((schema, index) => {
//it(expectedMessages[index], function () {
let validate = jsen(schema);
let valid = validate(data[index]);
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].message === expectedMessages[index]);
//});
});
}
// custom keyword messages
{
// uses custom messages on keywords
{
const schemas: any = [
{
type: 'string',
messages: { type: 'custom message for keyword "type"' }
},
{
enum: [1, 2, 3],
messages: { enum: 'custom message for keyword "enum"' }
},
{
minimum: 3,
messages: { minimum: 'custom message for keyword "minimum"' }
},
{
minimum: 3,
exclusiveMinimum: true,
messages: { exclusiveMinimum: 'custom message for keyword "exclusiveMinimum"' }
},
{
maximum: 10,
messages: { maximum: 'custom message for keyword "maximum"' }
},
{
maximum: 10,
exclusiveMaximum: true,
messages: { exclusiveMaximum: 'custom message for keyword "exclusiveMaximum"' }
},
{
multipleOf: 5,
messages: { multipleOf: 'custom message for keyword "multipleOf"' }
},
{
minLength: 3,
messages: { minLength: 'custom message for keyword "minLength"' }
},
{
maxLength: 5,
messages: { maxLength: 'custom message for keyword "maxLength"' }
},
{
pattern: '\\d+',
messages: { pattern: 'custom message for keyword "pattern"' }
},
{
format: 'email',
messages: { format: 'custom message for keyword "format"' }
},
{
minItems: 1,
messages: { minItems: 'custom message for keyword "minItems"' }
},
{
maxItems: 1,
messages: { maxItems: 'custom message for keyword "maxItems"' }
},
{
additionalItems: false,
items: [{ type: 'string' }],
messages: { additionalItems: 'custom message for keyword "additionalItems"' }
},
{
uniqueItems: true,
messages: { uniqueItems: 'custom message for keyword "uniqueItems"' }
},
{
minProperties: 1,
messages: { minProperties: 'custom message for keyword "minProperties"' }
},
{
maxProperties: 1,
messages: { maxProperties: 'custom message for keyword "maxProperties"' }
},
{
required: ['foo'],
messages: { required: 'custom message for keyword "required"' }
},
{
required: ['foo'],
properties: {
foo: {
messages: {
required: 'custom message for keyword "required"'
}
}
}
},
{
required: ['foo'],
properties: {
foo: {
messages: {
required: 'this custom message for keyword "required" is assigned'
}
}
},
messages: { required: 'this custom message for keyword "required" is NOT assigned' }
},
{
additionalProperties: false,
messages: { additionalProperties: 'custom message for keyword "additionalProperties"' }
},
{
dependencies: {
foo: ['bar']
},
messages: { dependencies: 'custom message for keyword "dependencies"' }
},
{
anyOf: [
{ type: 'string' },
{ type: 'integer' }
],
messages: { anyOf: 'custom message for keyword "anyOf"' }
},
{
oneOf: [
{ type: 'string' },
{ type: 'integer' }
],
messages: { oneOf: 'custom message for keyword "oneOf"' }
},
{
not: {
type: 'string'
},
messages: { not: 'custom message for keyword "not"' }
}
];
const data = [
123,
5,
1,
3,
11,
10,
12,
'ab',
'abcdef',
'abc',
'invalid email',
[],
[1, 2, 3],
['abc', 'def'],
[1, 2, 2],
{},
{ foo: 1, bar: 2 },
{},
{},
{},
{ foo: 'bar' },
{ foo: 'abc' },
null,
null,
'abc'
];
const expectedMessages = [
schemas[0].messages.type,
schemas[1].messages.enum,
schemas[2].messages.minimum,
schemas[3].messages.exclusiveMinimum,
schemas[4].messages.maximum,
schemas[5].messages.exclusiveMaximum,
schemas[6].messages.multipleOf,
schemas[7].messages.minLength,
schemas[8].messages.maxLength,
schemas[9].messages.pattern,
schemas[10].messages.format,
schemas[11].messages.minItems,
schemas[12].messages.maxItems,
schemas[13].messages.additionalItems,
schemas[14].messages.uniqueItems,
schemas[15].messages.minProperties,
schemas[16].messages.maxProperties,
schemas[17].messages.required,
schemas[18].properties.foo.messages.required,
schemas[19].properties.foo.messages.required,
schemas[20].messages.additionalProperties,
schemas[21].messages.dependencies,
schemas[22].messages.anyOf,
schemas[23].messages.oneOf,
schemas[24].messages.not
];
schemas.forEach((schema: any, index: number) => {
let validate = jsen(schema);
let valid = validate(data[index]);
console.assert(!valid);
console.assert(validate.errors[validate.errors.length - 1].message === expectedMessages[index]);
});
}
// does not use custom messages on keyword: items (object)
{
const schema = {
items: {
type: 'string',
messages: {
type: 'will be assigned'
}
},
messages: {
items: 'will not be assigned'
}
};
const validate = jsen(schema);
const valid = validate([123]);
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].message === 'will be assigned');
}
// does not use custom messages on keyword: items (array)
{
const schema = {
items: [{
type: 'string',
messages: {
type: 'will be assigned'
}
}],
messages: {
items: 'will not be assigned'
}
};
const validate = jsen(schema);
const valid = validate([123, 123]);
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].message === 'will be assigned');
}
// does not use custom messages on keyword: properties
{
const schema = {
properties: {
foo: {
type: 'number',
messages: {
type: 'will be assigned'
}
}
},
messages: {
properties: 'will not be assigned'
}
};
const validate = jsen(schema);
const valid = validate({ foo: 'bar' });
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].message === 'will be assigned');
}
// does not use custom messages on keyword: patternProperties
{
const schema = {
patternProperties: {
'^foo$': {
type: 'number',
messages: {
type: 'will be assigned'
}
}
},
messages: {
patternProperties: 'will not be assigned'
}
};
const validate = jsen(schema);
const valid = validate({ foo: 'bar' });
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].message === 'will be assigned');
}
// does not use custom messages on keyword: dependencies (schema)
{
const schema = {
dependencies: {
foo: {
minProperties: 2,
messages: {
minProperties: 'will be assigned'
}
}
},
messages: {
dependencies: 'will not be assigned'
}
};
const validate = jsen(schema);
const valid = validate({ foo: 'bar' });
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].message === 'will be assigned');
}
// does not use custom messages on keyword: allOf
{
const schema = {
dependencies: {
foo: {
minProperties: 2,
messages: {
minProperties: 'will be assigned'
}
}
},
allOf: [
{
minimum: 2,
messages: {
minimum: 'will not be assigned'
}
},
{
maximum: 5,
messages: {
maximum: 'will be assigned'
}
}
],
messages: {
allOf: 'will not be assigned'
}
};
const validate = jsen(schema);
const valid = validate(6);
console.assert(!valid);
console.assert(validate.errors.length === 1);
console.assert(validate.errors[0].message === 'will be assigned');
}
}
}
const doesThrow = (func: Function) => {
try {
func();
}
catch (e) {
return true;
}
return false;
};
const doesNotThrow = (func: Function) => {
return !doesThrow(func);
};
// fixes
{
// Fix broken inlining of regular expressions containing slashes (#15, #25)
{
const schema = {
type: 'string',
pattern: '^/dev/[^/]+(/[^/]+)*$'
};
console.assert(doesNotThrow(jsen(schema)));
}
// Fix code generation breaks when object properties in schema are not valid identifiers (#16)
{
const schema = {
type: 'object',
properties: {
123: {
type: 'boolean'
}
}
};
console.assert(doesNotThrow(() => {
let validate = jsen(schema);
console.assert(validate({ 123: true }));
})
);
}
// Fix cannot dereference schema when ids change resolution scope (#14)
{
let schema: any = {
$ref: '#child',
definitions: {
child: {
id: '#child',
type: 'string'
}
}
};
console.assert(doesNotThrow(() => {
let validate = jsen(schema);
console.assert(validate('abc'));
console.assert(!validate(123));
})
);
schema = {
$ref: '#child/definitions/subchild',
definitions: {
child: {
id: '#child',
definitions: {
subchild: {
type: 'number'
}
}
}
}
};
console.assert(doesThrow(() => {
let validate = jsen(schema);
})
);
}
// Fix recursive calls to the same cached $ref validator resets the error object
{
const schema = {
type: 'array',
items: {
type: 'object',
properties: {
foo: { $ref: '#' }
},
required: ['foo']
}
};
const validate = jsen(schema);
console.assert(validate([{ foo: [] }]));
console.assert(validate([{ foo: [{ foo: [] }] }]));
console.assert(!validate([{ bar: [] }]));
console.assert(!validate([{ foo: [{ foo: [] }, { bar: [] }] }])); // Bug! False positive
}
}
// format
{
// date-time
{
const schema = { format: 'date-time' };
const validate = jsen(schema);
console.assert(validate(new Date().toJSON()));
console.assert(!validate(''));
console.assert(!validate(new Date().toUTCString()));
console.assert(!validate(new Date().toLocaleDateString()));
console.assert(!validate(new Date().toTimeString()));
}
// uri
{
const schema = { format: 'uri' };
const validate = jsen(schema);
console.assert(validate('http://google.com'));
console.assert(validate('ftp://my-site'));
console.assert(validate('custom://my-site/long/$cr@mbl3d/u_r-l?with=query%20string'));
console.assert(validate('//no-scheme-here'));
console.assert(!validate(''));
console.assert(!validate('google'));
console.assert(!validate('/google'));
console.assert(!validate('://google'));
console.assert(!validate('http://google.com/no space allowed'));
}
// email
{
const schema = { format: 'email' };
const validate = jsen(schema);
const maxLongHostname1 = new Array(5).join('.' + new Array(64).join('a')).substr(1); // 255 chars (4 groups x 63 chars)
const maxLongHostname2 = new Array(9).join('.' + new Array(32).join('a')).substr(1); // 255 chars (8 groups x 31 chars)
console.assert(validate('me@domain'));
console.assert(validate('first.last+plus-dash#hash!bang$dollar%percent&amp\'quote*star/dash=equal?question^pow_under`backtick{brace}|bar~tilde@domain'));
console.assert(validate('me@domain.with.multiple.subdomains'));
console.assert(validate('me@domain-parts.may.contain-dashes'));
console.assert(validate('me@a-single-domain-part-can-be-up-to-sixty-three-characters-long63'));
console.assert(validate('me@' + maxLongHostname1));
console.assert(validate('me@' + maxLongHostname2));
console.assert(!validate(''));
console.assert(!validate('qu"ote\'s@domain'));
console.assert(!validate('me@no_underscores+or?special$chars'));
console.assert(!validate('me@ends-with-dash-'));
console.assert(!validate('me@-starts-with-dash'));
console.assert(!validate('me@asingle-domain-part-cannot-be-longer-than-sixty-three-characters'));
// These verify that a hostname cannot be longer than 255 chars in total. However,
// maximum string length verification cannot be performed in the same regex, so
// these test cases fail. Users must additionall use the `maxlength` keyword in this case.
// assert(!validate('me@' + maxLongHostname1 + '.a'));
// assert(!validate('me@' + maxLongHostname2 + '.a'));
}
// ipv4
{
const schema = { format: 'ipv4' };
const validate = jsen(schema);
console.assert(validate('0.0.0.0'));
console.assert(validate('255.255.255.255'));
console.assert(validate('127.0.0.1'));
console.assert(!validate(''));
console.assert(!validate('...'));
console.assert(!validate('0.0.0.-1'));
console.assert(!validate('0.0.-1.0'));
console.assert(!validate('0.-1.0.0'));
console.assert(!validate('-1.0.0.0'));
console.assert(!validate('256.0.0.0'));
console.assert(!validate('0.256.0.0'));
console.assert(!validate('0.0.256.0'));
console.assert(!validate('0.0.0.256'));
}
// ipv6
{
const schema = { format: 'ipv6' };
const validate = jsen(schema);
console.assert(validate('1:2:3:4:5:6:7:8'));
console.assert(validate('1::'));
console.assert(validate('1:2:3:4:5:6:7::'));
console.assert(validate('1::8'));
console.assert(validate('1:2:3:4:5:6::8'));
console.assert(validate('1::7:8'));
console.assert(validate('1:2:3:4:5::7:8'));
console.assert(validate('1:2:3:4:5::8'));
console.assert(validate('1::6:7:8'));
console.assert(validate('1:2:3:4::6:7:8'));
console.assert(validate('1:2:3:4::8'));
console.assert(validate('1::5:6:7:8'));
console.assert(validate('1:2:3::5:6:7:8'));
console.assert(validate('1:2:3::8'));
console.assert(validate('1::4:5:6:7:8'));
console.assert(validate('1:2::4:5:6:7:8'));
console.assert(validate('1:2::8'));
console.assert(validate('1::3:4:5:6:7:8'));
console.assert(validate('1::8'));
console.assert(validate('::2:3:4:5:6:7:8'));
console.assert(validate('::8'));
console.assert(validate('::'));
// link-local IPv6 addresses with zone index
console.assert(validate('fe80::7:8%eth0'));
console.assert(validate('fe80::7:8%1'));
// IPv4-mapped IPv6 addresses and IPv4-translated addresses
console.assert(validate('::255.255.255.255'));
console.assert(validate('::ffff:255.255.255.255'));
console.assert(validate('::ffff:0:255.255.255.255'));
// IPv4-Embedded IPv6 Address
console.assert(validate('2001:db8:3:4::192.0.2.33'));
console.assert(validate('64:ff9b::192.0.2.33'));
console.assert(!validate(''));
console.assert(!validate('::_'));
// TODO: we may need more invalid cases here
}
// hostname
{
const schema = { format: 'hostname' },
validate = jsen(schema),
maxLong1 = new Array(5).join('.' + new Array(64).join('a')).substr(1), // 255 chars (4 groups x 63 chars)
maxLong2 = new Array(9).join('.' + new Array(32).join('a')).substr(1); // 255 chars (8 groups x 31 chars)
console.assert(validate('my.host'));
console.assert(validate('host'));
console.assert(validate('domain.with.multiple.subdomains'));
console.assert(validate('domain-parts.may.contain-dashes'));
console.assert(validate('a-single-domain-part-can-be-up-to-sixty-three-characters-long63'));
console.assert(validate(maxLong1));
console.assert(validate(maxLong2));
console.assert(!validate(''));
console.assert(!validate('me@domain'));
console.assert(!validate('qu"ote\'s'));
console.assert(!validate('no_underscores+or?special$chars'));
console.assert(!validate('ends-with-dash-'));
console.assert(!validate('-starts-with-dash'));
console.assert(!validate('asingle-domain-part-cannot-be-longer-than-sixty-three-characters'));
// These verify that a hostname cannot be longer than 255 chars in total. However,
// maximum string length verification cannot be performed in the same regex, so
// these test cases fail. Users must additionall use the `maxlength` keyword in this case.
// assert(!validate(maxLong1 + '.a'));
// assert(!validate(maxLong2 + '.a'));
}
// custom format
{
// accepts string
{
const schema = { format: 'custom' },
custom = '^\\d+$',
validate = jsen(schema, {
formats: {
custom: custom
}
});
console.assert(validate('123'));
console.assert(!validate('a123'));
}
// accepts regex
{
const schema = { format: 'custom' },
custom = /^\d+$/,
validate = jsen(schema, {
formats: {
custom: custom
}
});
console.assert(validate('123'));
console.assert(!validate('a123'));
}
// accepts function
{
let schema = { format: 'custom' },
callCount = 0,
custom = (value: any, childSchema: any) => {
console.assert(value.indexOf('123') > -1);
console.assert(childSchema === schema);
callCount++;
return /^\d+$/.test(value);
},
validate = jsen(schema, {
formats: {
custom: custom
}
});
console.assert(validate('123'));
console.assert(!validate('a123'));
console.assert(callCount === 2);
}
// is run for all types
{
let schema = { format: 'custom' },
callCount = 0,
options = <JsenSettings>{
formats: {
custom: () => {
callCount++;
return true;
}
}
},
validate = jsen(schema, options),
data = [
undefined,
null,
'abc',
123,
Math.PI,
true,
false,
{},
[],
new Date()
];
data.forEach((dataItem) => {
validate(dataItem);
console.assert(callCount === 1);
callCount = 0;
});
}
// is not run if a built-in keyword fails
{
let schema = {
format: 'custom',
type: 'number',
maximum: 10
},
callCount = 0,
options = <JsenSettings>{
formats: {
custom: () => {
callCount++;
return true;
}
}
},
validate = jsen(schema, options);
console.assert(!validate(123));
console.assert(callCount === 0);
console.assert(validate(7));
console.assert(callCount === 1);
}
// common scenarios
{
// verify passwords match
{
let schema = {
description: 'User account creation form',
type: 'object',
properties: {
password: {
type: 'string',
minLength: 8
},
password_confirm: {
type: 'string',
minLength: 8
}
},
format: 'passwordsMatch'
},
options = <JsenSettings>{
formats: {
passwordsMatch: (obj: any) => {
callCount++;
return obj.password === obj.password_confirm;
}
}
},
data = {
password: '1234567',
password_confirm: '1234567'
},
validate = jsen(schema, options),
callCount = 0;
console.assert(!validate(data)); // minLength validator failed
console.assert(callCount === 0);
data.password += '8';
data.password_confirm += '9';
console.assert(!validate(data)); // custom validator failed
console.assert(callCount === 1);
data.password_confirm = data.password;
console.assert(validate(data)); // OK
console.assert(callCount === 2);
}
}
}
}
// option: greedy
{
// validates as many keywords as possible
{
let schema = {
type: 'object',
properties: {
test1: {
type: 'string'
},
test2: {
type: 'object',
properties: {
test21: {
type: 'number'
}
}
},
test3: {
type: 'number'
},
test4: { $ref: '#external' }
},
additionalProperties: false
},
options = {
greedy: true,
schemas: {
external: {
type: 'string'
}
}
},
validate = jsen(schema, options),
invalidTest = {
test1: 1,
test2: '2',
test3: 'j',
test4: 4
},
ret = validate(invalidTest);
console.assert(!ret); // false
console.assert(JSON.stringify(validate.errors)
=== JSON.stringify([
{ path: 'test1', keyword: 'type' },
{ path: 'test2', keyword: 'type' },
{ path: 'test3', keyword: 'type' },
{ path: 'test4', keyword: 'type' }
])
);
delete options.greedy;
validate = jsen(schema, options);
ret = validate(invalidTest);
console.assert(!ret); // false
console.assert(JSON.stringify(validate.errors)
=== JSON.stringify([{ path: 'test1', keyword: 'type' }])
);
}
// does not descend into invalid objects
{
let schema = {
type: 'object',
properties: {
test1: { type: 'object' },
test2: {
required: ['foo']
},
test3: {
properties: {
foo: { type: 'string' }
}
},
test4: {
type: 'array',
items: {
type: 'object',
required: ['foo']
}
}
}
},
options = { greedy: true },
data = {
test1: 123,
test2: {},
test3: 123,
test4: [{}, { foo: 123 }, null]
},
validate = jsen(schema, options),
ret = validate(data);
console.assert(!ret);
console.assert(JSON.stringify(validate.errors)
=== JSON.stringify([
{ path: 'test1', keyword: 'type' },
{ path: 'test2.foo', keyword: 'required' },
{ path: 'test4.0.foo', keyword: 'required' },
{ path: 'test4.2', keyword: 'type' }
])
);
}
}
// jsen
{
// is a function
{
console.assert(typeof jsen === "function");
}
// throws if schema is not an object
{
console.assert(doesThrow(jsen()));
console.assert(doesThrow(jsen(null)));
console.assert(doesThrow(jsen(false)));
console.assert(doesThrow(jsen(123)));
console.assert(doesThrow(jsen("abc")));
console.assert(doesThrow(jsen([])));
console.assert(doesNotThrow(jsen({})));
}
// produces a function
{
const validate = jsen({});
console.assert(typeof validate === 'function');
console.assert(validate() === true);
// console.assert(validate.error === null);
}
}
// type: integer
{
// required
{
const schema = { type: 'integer' },
validate = jsen(schema);
console.assert(!validate());
console.assert(!validate(null));
console.assert(validate(123));
}
// nullable
{
const schema = { type: ['integer', 'null'] },
validate = jsen(schema);
console.assert(!validate(undefined));
console.assert(validate(null));
console.assert(validate(123));
}
// type
{
const schema = { type: 'integer' },
validate = jsen(schema);
console.assert(!validate('123'));
console.assert(!validate(true));
console.assert(!validate(false));
console.assert(!validate([]));
console.assert(!validate({}));
console.assert(!validate(Math.PI));
console.assert(validate(13));
}
// enum
{
const schema = { type: 'integer', enum: [1, 3, 5, 7] },
validate = jsen(schema);
console.assert(!validate(4));
console.assert(validate(5));
}
// minimum
{
const schema = { type: 'integer', minimum: 7 },
validate = jsen(schema);
console.assert(!validate(6));
console.assert(validate(7));
console.assert(validate(999));
}
// exclusiveMinimum
{
const schema = {
type: 'integer',
minimum: 7,
exclusiveMinimum: true
},
validate = jsen(schema);
console.assert(!validate(6));
console.assert(!validate(7));
console.assert(validate(8));
console.assert(validate(999));
}
// maximum
{
const schema = { type: 'integer', maximum: 77 },
validate = jsen(schema);
console.assert(!validate(78));
console.assert(validate(-12));
console.assert(validate(76));
console.assert(validate(77));
}
// exclusiveMaximum
{
const schema = {
type: 'integer',
maximum: 77,
exclusiveMaximum: true
},
validate = jsen(schema);
console.assert(!validate(77));
console.assert(!validate(78));
console.assert(validate(-12));
console.assert(validate(75));
console.assert(validate(76));
}
// multipleOf
{
const schema = { type: 'integer', multipleOf: 7 },
validate = jsen(schema);
console.assert(!validate(8));
console.assert(validate(14));
console.assert(validate(-49));
console.assert(validate(77));
}
}
// missing $ref
{
// passes validation with ignore missing $ref
{
let schema = {
type: 'object',
properties: {
test1: { $ref: '#external1' },
test2: {
type: 'number'
},
test3: { $ref: '#external3' } //missing
},
additionalProperties: false
},
external1 = {
type: 'object',
properties: {
test11: { $ref: '#external11' }, //missing
test12: {
type: 'number'
},
test13: { $ref: '#external11' } //duplicate
}
},
validate = jsen(schema, {
schemas: {
external1: external1
},
missing$Ref: true
}),
missingTest = {
test1: {
test11: 'missing',
test12: 5,
test13: 'missing too'
},
test2: 2,
test3: 3
},
invalidTest = {
test1: {
test11: 'missing',
test12: 5,
test13: 'missing too'
},
test2: 'fail',
test3: 3
},
ret: boolean;
ret = validate(missingTest);
console.assert(ret); // true
ret = validate(invalidTest);
console.assert(!ret); // !false
}
}
// multi schema
{
// allOf
{
const schema = {
allOf: [
{ type: 'number' },
{ type: 'integer' }
]
},
validate = jsen(schema);
console.assert(!validate(null));
console.assert(!validate(Math.PI));
console.assert(validate(0));
console.assert(validate(777));
console.assert(validate(-9));
}
// anyOf
{
const schema = {
anyOf: [
{ type: 'string' },
{ type: 'number' }
]
},
validate = jsen(schema);
console.assert(!validate(null));
console.assert(!validate(true));
console.assert(!validate({}));
console.assert(!validate([]));
console.assert(validate('abc'));
console.assert(validate(123));
console.assert(validate(''));
console.assert(validate(0));
}
// oneOf
{
const schema = {
oneOf: [
{ type: 'number', maximum: 5 },
{ type: 'number', minimum: 3 }
]
},
validate = jsen(schema);
console.assert(!validate(null));
console.assert(!validate(true));
console.assert(!validate({}));
console.assert(!validate([]));
// matches both validators
console.assert(!validate(3));
console.assert(validate(0));
console.assert(validate(1));
console.assert(validate(2));
console.assert(validate(6));
console.assert(validate(17));
}
// not
{
const schema = {
not: {
type: 'array'
}
},
validate = jsen(schema);
console.assert(!validate([]));
console.assert(validate(0));
console.assert(validate(false));
console.assert(validate('abc'));
console.assert(validate({}));
console.assert(validate(null));
console.assert(validate());
}
}
// type: null
{
// required
{
const schema = { type: 'null' },
validate = jsen(schema);
console.assert(!validate(undefined));
console.assert(validate(null));
}
// type
{
const schema = { type: 'null' },
validate = jsen(schema);
console.assert(!validate('123'));
console.assert(!validate([]));
console.assert(!validate({}));
console.assert(!validate(Math.PI));
console.assert(validate(null));
}
}
// type: number
{
// required
{
const schema = { type: 'number' },
validate = jsen(schema);
console.assert(!validate());
console.assert(!validate(null));
console.assert(validate(Math.PI));
console.assert(validate(123));
}
// nullable
{
const schema = { type: ['number', 'null'] },
validate = jsen(schema);
console.assert(!validate(undefined));
console.assert(validate(null));
console.assert(validate(Math.PI));
}
// type
{
const schema = { type: 'number' },
validate = jsen(schema);
console.assert(!validate('123'));
console.assert(!validate(true));
console.assert(!validate(false));
console.assert(!validate([]));
console.assert(!validate({}));
console.assert(validate(13));
console.assert(validate(17.8));
console.assert(validate(Math.PI));
}
// enum
{
const schema = {
type: 'number',
enum: [1, Math.E, 3, 5, 7]
},
validate = jsen(schema);
console.assert(!validate(4));
console.assert(!validate(Math.PI));
console.assert(validate(5));
console.assert(validate(Math.E));
}
// minimum
{
const schema = { type: 'number', minimum: 7 },
validate = jsen(schema);
console.assert(!validate(6));
console.assert(!validate(Math.PI));
console.assert(validate(7));
console.assert(validate(999));
}
// exclusiveMinimum
{
const schema = {
type: 'number',
minimum: 7,
exclusiveMinimum: true
},
validate = jsen(schema);
console.assert(!validate(6));
console.assert(!validate(7));
console.assert(!validate(Math.PI));
console.assert(validate(8));
console.assert(validate(999));
}
// maximum
{
const schema = { type: 'number', maximum: 77 },
validate = jsen(schema);
console.assert(!validate(77.000001));
console.assert(!validate(78));
console.assert(validate(-12));
console.assert(validate(76));
console.assert(validate(77));
console.assert(validate(Math.PI));
}
// exclusiveMaximum
{
const schema = {
type: 'number',
maximum: 77,
exclusiveMaximum: true
},
validate = jsen(schema);
console.assert(!validate(77));
console.assert(!validate(78));
console.assert(validate(-12));
console.assert(validate(75));
console.assert(validate(76));
console.assert(validate(76.99999));
}
// multipleOf
{
let schema = { type: 'number', multipleOf: 7 },
validate = jsen(schema);
console.assert(!validate(8));
console.assert(validate(14));
console.assert(validate(-49));
console.assert(validate(77));
schema = {
type: 'number',
multipleOf: 3.14 // Math.PI
};
validate = jsen(schema);
console.assert(!validate(2.5));
console.assert(validate(9.42)); // 3 * Math.PI
}
// fix multipleOf doesn't validate data for decimal point (#1)
{
const schema = { type: 'number', multipleOf: 0.01 },
validate = jsen(schema);
console.assert(validate(18.15));
}
}
// type: object
{
// required
{
const schema = { type: 'object' },
validate = jsen(schema);
console.assert(!validate());
console.assert(!validate(null));
console.assert(validate({}));
}
// nullable
{
const schema = { type: ['object', 'null'] },
validate = jsen(schema);
console.assert(!validate(undefined));
console.assert(validate(null));
console.assert(validate({}));
}
// type
{
const schema = { type: 'object' },
validate = jsen(schema);
console.assert(!validate('123'));
console.assert(!validate(false));
console.assert(!validate([]));
console.assert(!validate(Math.PI));
console.assert(validate({}));
console.assert(jsen({ type: 'object', properties: {} }, {})());
}
// maxProperties
{
const schema = { type: 'object', maxProperties: 3 },
validate = jsen(schema);
console.assert(!validate({ a: 1, b: 2, c: 3, d: 4 }));
console.assert(validate({}));
console.assert(validate({ a: 1 }));
console.assert(validate({ a: 1, b: 2 }));
console.assert(validate({ a: 1, b: 2, c: 3 }));
}
// minProperties
{
const schema = { type: 'object', minProperties: 2 },
validate = jsen(schema);
console.assert(!validate({}));
console.assert(!validate({ a: 1 }));
console.assert(validate({ a: 1, b: 2 }));
console.assert(validate({ a: 1, b: 2, c: 3 }));
}
// required properties
{
const schema = {
type: 'object',
properties: {
a: { type: 'string' },
b: { type: 'number' },
c: { type: 'boolean' }
},
required: ['a', 'b']
},
validate = jsen(schema);
console.assert(!validate({}));
console.assert(!validate({ c: true }));
console.assert(!validate({ a: 'abc', c: true }));
console.assert(!validate({ b: 123, c: true }));
console.assert(!validate({ a: 'abc', b: undefined }));
console.assert(validate({ a: 'abc', b: 123 }));
console.assert(validate({ a: 'abc', b: 123, c: true }));
}
// additionalProperties
{
let schema = {
type: 'object',
properties: {
a: { type: 'string' },
b: { type: 'number' }
},
additionalProperties: true
},
validate = jsen(schema);
console.assert(validate({ a: 'abc' }));
console.assert(validate({ b: 123 }));
console.assert(validate({ a: 'abc', b: 123 }));
console.assert(validate({ a: 'abc', b: 123, c: true }));
schema.additionalProperties = false;
validate = jsen(schema);
console.assert(!validate({ c: true }));
console.assert(!validate({ a: 'abc', b: 123, c: true }));
console.assert(validate({ a: 'abc', b: 123 }));
console.assert(jsen({ type: 'object', additionalProperties: false })({}));
}
// additionalProperties as schema
{
const schema = {
type: 'object',
properties: {
a: { type: 'string' },
b: { type: 'number' }
},
additionalProperties: {
type: 'boolean'
}
},
validate = jsen(schema);
console.assert(!validate({ a: 'abc', b: 123, c: 123 }));
console.assert(validate({ a: 'abc', b: 123, c: false }));
}
// additionalProperties with patternProperties
{
let schema = {
type: 'object',
properties: {
a: { type: 'string' }
},
patternProperties: {
'^b': { type: 'number' }
},
additionalProperties: true
},
validate = jsen(schema);
console.assert(validate({ a: 'abc' }));
console.assert(validate({ b: 123 }));
console.assert(validate({ a: 'abc', b: 123, bar: Math.E, baz: Math.PI }));
console.assert(validate({ a: 'abc', baz: 123, c: true }));
schema.additionalProperties = false;
validate = jsen(schema);
console.assert(!validate({ c: true }));
console.assert(!validate({ a: 'abc', bar: 123, c: true }));
console.assert(validate({ a: 'abc', baz: 123 }));
console.assert(jsen({ type: 'object', additionalProperties: false })({}));
}
// patternProperties
{
const schema = {
type: 'object',
patternProperties: {
'^a': { type: 'string' },
'^b': { type: 'number' }
}
},
validate = jsen(schema);
console.assert(!validate({ a: 123 }));
console.assert(!validate({ b: 'abc' }));
console.assert(validate({}));
console.assert(validate({ a: 'abc' }));
console.assert(validate({ b: 123 }));
console.assert(validate({ a: 'abc', b: 123 }));
}
// dependencies: schema
{
const schema = {
type: 'object',
properties: {
a: { type: 'string' },
b: { type: 'number' }
},
dependencies: {
a: {
type: 'object',
required: ['c'],
properties: {
c: { type: 'boolean' }
}
},
b: {
type: 'object',
required: ['f'],
properties: {
f: { type: 'null' }
}
},
g: {
type: 'object',
required: ['b'],
properties: {
b: {
type: 'integer'
}
}
}
}
},
validate = jsen(schema);
console.assert(!validate({ a: 'abc' }));
console.assert(!validate({ a: 'abc', c: 123 }));
console.assert(!validate({ b: Math.PI, f: false }));
console.assert(!validate({ b: Math.PI, g: null }));
console.assert(validate({}));
console.assert(validate({ a: 'abc', c: false }));
console.assert(validate({ b: Math.PI, f: null }));
console.assert(validate({ b: 123, g: 'any value', f: null }));
}
// dependencies: property
{
const schema = {
type: 'object',
properties: {
a: { type: 'string' },
b: { type: 'number' },
c: { type: 'boolean' }
},
dependencies: {
a: ['b', 'c']
}
},
validate = jsen(schema);
console.assert(!validate({ a: 'abc' }));
console.assert(!validate({ a: 'abc', b: 123 }));
console.assert(validate({}));
console.assert(validate({ a: 'abc', b: 123, c: false }));
}
// nested graph
{
const schema = {
type: ['object', 'null'],
properties: {
a: { type: 'string' },
b: { type: 'number' },
c: {
type: 'array',
items: { type: 'boolean' }
}
},
required: ['a']
},
validate = jsen(schema);
console.assert(!validate());
console.assert(!validate({}));
console.assert(!validate({ a: 123 }));
console.assert(!validate({ a: 'abc', b: false }));
console.assert(!validate({ a: 'abc', c: [null] }));
console.assert(validate(null));
console.assert(validate({ a: 'abc', b: 123.4, c: [true, false] }));
console.assert(validate({ a: 'abc', b: 0 }));
console.assert(validate({ a: 'abc', c: [true, false] }));
console.assert(validate({ a: 'abc', c: [] }));
}
}
// $ref
{
// throws if string is not in correct format
{
console.assert(doesThrow(
jsen({ $ref: '' })
));
console.assert(doesThrow(
jsen({ $ref: '#double//slash' })
));
console.assert(doesThrow(
jsen({ $ref: '#ends/with/slash/' })
));
console.assert(doesThrow(
// invalid reference, non-existent schema properties
jsen({ $ref: '#a/b/c' })
));
console.assert(doesNotThrow(
// schema resolves to itself
jsen({ $ref: '#' })
));
console.assert(doesNotThrow(
jsen({
a: {
b: {
c: {
type: 'any'
}
}
},
$ref: '#/a/b/c'
})
));
console.assert(doesNotThrow(
jsen({
arr: [
{ value: { type: 'string' } },
{ value: { type: 'number' } },
{ value: { type: 'boolean' } }
],
type: 'object',
properties: {
a: { $ref: '#arr/2/value' }
}
})
));
}
// external schema
{
// finds external schema with a hash
{
const external = { type: 'string' },
schema = { $ref: '#external' },
validate = jsen(schema, {
schemas: {
external: external
}
});
console.assert(validate('abc'));
console.assert(!validate(123));
}
// finds external schema without a hash
{
const external = { type: 'string' },
schema = { $ref: 'external' },
validate = jsen(schema, {
schemas: {
external: external
}
});
console.assert(validate('abc'));
console.assert(!validate(123));
}
// throws when no external schema found
{
const schema = { $ref: '#external' };
console.assert(doesNotThrow(
jsen(schema)
));
}
// own property takes precendence over external schema
{
const external = { type: 'string' },
schema = {
external: { type: 'number' },
$ref: '#external'
},
validate = jsen(schema, {
schemas: {
external: external
}
});
console.assert(!validate('abc'));
console.assert(validate(123));
}
// external schemas have their own dereferencing scope
{
const external = {
inner: { type: 'string' },
$ref: '#inner'
},
schema = {
inner: { type: 'number' },
$ref: '#external'
},
validate = jsen(schema, {
schemas: {
external: external
}
});
console.assert(validate('abc'));
console.assert(!validate(123));
}
}
}
// type: string
{
// required
{
const schema = { type: 'string' },
validate = jsen(schema);
console.assert(!validate());
console.assert(!validate(null));
console.assert(validate('abc'));
}
// nullable
{
const schema = { type: ['string', 'null'] },
validate = jsen(schema);
console.assert(!validate(undefined));
console.assert(validate(null));
console.assert(validate(''));
}
// type
{
const schema = { type: 'string' },
validate = jsen(schema);
console.assert(!validate(123));
console.assert(!validate(true));
console.assert(!validate(false));
console.assert(!validate(0));
console.assert(!validate([]));
console.assert(!validate({}));
console.assert(validate('abc'));
}
// enum
{
const schema = { type: 'string', enum: ['a', 'b', 'c'] },
validate = jsen(schema);
console.assert(!validate('not in enum'));
console.assert(validate('b'));
}
// minLength
{
const schema = { type: 'string', minLength: 10 },
validate = jsen(schema);
console.assert(!validate('too short'));
console.assert(validate('just long enough'));
}
// maxLength
{
const schema = { type: 'string', maxLength: 12 },
validate = jsen(schema);
console.assert(!validate('this string is too long'));
console.assert(validate('short enough'));
}
// pattern
{
let schema: any = { type: 'string', pattern: '\\d' },
validate = jsen(schema);
console.assert(!validate('a'));
console.assert(validate('1'));
schema = { type: 'string', pattern: /\d/ };
validate = jsen(schema);
console.assert(!validate('a'));
console.assert(validate('1'));
}
}
// unique
{
// filters unique values
{
const inputs = [
[1, 'a', 3, false, null, undefined],
['abc', 123, true, 123, false, Math.PI, 'abc', true, null, null]
],
expected = [
[1, 'a', 3, false, null, undefined],
['abc', 123, true, false, Math.PI, null]
];
for (let i = 0; i < inputs.length; i++) {
console.assert(JSON.stringify(jsen.unique(inputs[i])) === JSON.stringify(expected[i]));
}
}
// performs deep equality checks
{
const input: any = [
{},
{ a: 1 },
{ b: { c: { d: 123, f: null }, e: 'abc' } },
[1, 2, 3],
[{ a: 213 }],
{ b: 1 },
{ a: 1, b: undefined },
{ b: { e: 'abc', c: { f: null, d: 123 } } },
[1, 2, 3],
[{ a: 213 }]
],
expected: any = [
{},
{ a: 1 },
{ b: { c: { d: 123, f: null }, e: 'abc' } },
[1, 2, 3],
[{ a: 213 }],
{ b: 1 },
{ a: 1, b: undefined }
];
console.assert(JSON.stringify(jsen.unique(input)) === JSON.stringify(expected));
}
// unique.findIndex
{
// finds an item index with comparator
{
const arr = [{}, { a: 1 }, { a: 1, b: 2 }],
expected = 2,
comparator = (obj1: any, obj2: any) => {
return obj1.a === obj2.a && obj1.b === obj2.b;
};
console.assert(jsen.unique.findIndex(arr, { a: 1, b: 2 }, comparator) === expected);
}
// returns -1 when item cannot be found
{
const arr = [{}, { a: 1 }, { a: 1, b: 2 }],
expected = -1,
comparator = (obj1: any, obj2: any) => {
return obj1.a === obj2.a && obj1.b === obj2.b;
};
console.assert(jsen.unique.findIndex(arr, { a: 1, b: null }, comparator) === expected);
}
}
}