123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- 'use strict';
- var Ajv = require('./ajv');
- require('./chai').should();
- var coercionRules = {
- 'string': {
- 'number': [
- { from: 1, to: '1' },
- { from: 1.5, to: '1.5' },
- { from: 2e100, to: '2e+100' }
- ],
- 'boolean': [
- { from: false, to: 'false' },
- { from: true, to: 'true' }
- ],
- 'null': [
- { from: null, to: '' }
- ],
- 'object': [
- { from: {}, to: undefined }
- ],
- 'array': [
- { from: [], to: undefined },
- { from: [1], to: undefined }
- ]
- },
- 'number': {
- 'string': [
- { from: '1', to: 1 },
- { from: '1.5', to: 1.5 },
- { from: '2e10', to: 2e10 },
- { from: '1a', to: undefined },
- { from: 'abc', to: undefined },
- { from: '', to: undefined }
- ],
- 'boolean': [
- { from: false, to: 0 },
- { from: true, to: 1 }
- ],
- 'null': [
- { from: null, to: 0 }
- ],
- 'object': [
- { from: {}, to: undefined }
- ],
- 'array': [
- { from: [], to: undefined },
- { from: [true], to: undefined }
- ]
- },
- 'integer': {
- 'string': [
- { from: '1', to: 1 },
- { from: '1.5', to: undefined },
- { from: '2e10', to: 2e10 },
- { from: '1a', to: undefined },
- { from: 'abc', to: undefined },
- { from: '', to: undefined }
- ],
- 'boolean': [
- { from: false, to: 0 },
- { from: true, to: 1 }
- ],
- 'null': [
- { from: null, to: 0 }
- ],
- 'object': [
- { from: {}, to: undefined }
- ],
- 'array': [
- { from: [], to: undefined },
- { from: ['1'], to: undefined }
- ]
- },
- 'boolean': {
- 'string': [
- { from: 'false', to: false },
- { from: 'true', to: true },
- { from: '', to: undefined },
- { from: 'abc', to: undefined }
- ],
- 'number': [
- { from: 0, to: false },
- { from: 1, to: true },
- { from: 2, to: undefined },
- { from: 2.5, to: undefined }
- ],
- 'null': [
- { from: null, to: false }
- ],
- 'object': [
- { from: {}, to: undefined }
- ],
- 'array': [
- { from: [], to: undefined },
- { from: [0], to: undefined }
- ]
- },
- 'null': {
- 'string': [
- { from: '', to: null },
- { from: 'abc', to: undefined },
- { from: 'null', to: undefined }
- ],
- 'number': [
- { from: 0, to: null },
- { from: 1, to: undefined }
- ],
- 'boolean': [
- { from: false, to: null },
- { from: true, to: undefined }
- ],
- 'object': [
- { from: {}, to: undefined }
- ],
- 'array': [
- { from: [], to: undefined },
- { from: [null], to: undefined }
- ]
- },
- 'array': {
- 'all': [
- { type: 'string', from: 'abc', to: undefined },
- { type: 'number', from: 1, to: undefined },
- { type: 'boolean', from: true, to: undefined },
- { type: 'null', from: null, to: undefined },
- { type: 'object', from: {}, to: undefined }
- ]
- },
- 'object': {
- 'all': [
- { type: 'string', from: 'abc', to: undefined },
- { type: 'number', from: 1, to: undefined },
- { type: 'boolean', from: true, to: undefined },
- { type: 'null', from: null, to: undefined },
- { type: 'array', from: [], to: undefined }
- ]
- }
- };
- var coercionArrayRules = JSON.parse(JSON.stringify(coercionRules));
- coercionArrayRules.string.array = [
- { from: ['abc'], to: 'abc' },
- { from: [123], to: '123' },
- { from: ['abc', 'def'], to: undefined },
- { from: [], to: undefined }
- ];
- coercionArrayRules.number.array = [
- { from: [1.5], to: 1.5 },
- { from: ['1.5'], to: 1.5 }
- ];
- coercionArrayRules.integer.array = [
- { from: [1], to: 1 },
- { from: ['1'], to: 1 },
- { from: [true], to: 1 },
- { from: [null], to: 0 }
- ];
- coercionArrayRules.boolean.array = [
- { from: [true], to: true },
- { from: ['true'], to: true },
- { from: [1], to: true }
- ];
- coercionArrayRules.null.array = [
- { from: [null], to: null },
- { from: [''], to: null },
- { from: [0], to: null },
- { from: [false], to: null }
- ];
- coercionArrayRules.object.array = [
- { from: [{}], to: undefined }
- ];
- coercionArrayRules.array = {
- 'string': [
- {from: 'abc', to: ['abc']}
- ],
- 'number': [
- {from: 1, to: [1]}
- ],
- 'boolean': [
- {from: true, to: [true]}
- ],
- 'null': [
- {from: null, to: [null]}
- ],
- 'object': [
- {from: {}, to: undefined}
- ]
- };
- describe('Type coercion', function () {
- var ajv, fullAjv, instances;
- beforeEach(function() {
- ajv = new Ajv({ coerceTypes: true, verbose: true });
- fullAjv = new Ajv({ coerceTypes: true, verbose: true, allErrors: true });
- instances = [ ajv, fullAjv ];
- });
- it('should coerce scalar values', function() {
- testRules(coercionRules, function (test, schema, canCoerce/*, toType, fromType*/) {
- instances.forEach(function (_ajv) {
- var valid = _ajv.validate(schema, test.from);
- //if (valid !== canCoerce) console.log('true', toType, fromType, test, ajv.errors);
- valid. should.equal(canCoerce);
- });
- });
- });
- it('should coerce scalar values (coerceTypes = array)', function() {
- ajv = new Ajv({ coerceTypes: 'array', verbose: true });
- fullAjv = new Ajv({ coerceTypes: 'array', verbose: true, allErrors: true });
- instances = [ ajv, fullAjv ];
- testRules(coercionArrayRules, function (test, schema, canCoerce, toType, fromType) {
- instances.forEach(function (_ajv) {
- var valid = _ajv.validate(schema, test.from);
- if (valid !== canCoerce) console.log(toType, '.', fromType, test, schema, ajv.errors);
- valid. should.equal(canCoerce);
- });
- });
- });
- it('should coerce values in objects/arrays and update properties/items', function() {
- testRules(coercionRules, function (test, schema, canCoerce/*, toType, fromType*/) {
- var schemaObject = {
- type: 'object',
- properties: {
- foo: schema
- }
- };
- var schemaArray = {
- type: 'array',
- items: schema
- };
- var schemaArrObj = {
- type: 'array',
- items: schemaObject
- };
- instances.forEach(function (_ajv) {
- testCoercion(_ajv, schemaArray, [ test.from ], [ test.to ]);
- testCoercion(_ajv, schemaObject, { foo: test.from }, { foo: test.to });
- testCoercion(_ajv, schemaArrObj, [ { foo: test.from } ], [ { foo: test.to } ]);
- });
- function testCoercion(_ajv, _schema, fromData, toData) {
- var valid = _ajv.validate(_schema, fromData);
- //if (valid !== canCoerce) console.log(schema, fromData, toData);
- valid. should.equal(canCoerce);
- if (valid) fromData.should.eql(toData);
- }
- });
- });
- it('should coerce to multiple types in order with number type', function() {
- var schema = {
- type: 'object',
- properties: {
- foo: {
- type: [ 'number', 'boolean', 'null' ]
- }
- }
- };
- instances.forEach(function (_ajv) {
- var data;
- _ajv.validate(schema, data = { foo: '1' }) .should.equal(true);
- data .should.eql({ foo: 1 });
- _ajv.validate(schema, data = { foo: '1.5' }) .should.equal(true);
- data .should.eql({ foo: 1.5 });
- _ajv.validate(schema, data = { foo: 'false' }) .should.equal(true);
- data .should.eql({ foo: false });
- _ajv.validate(schema, data = { foo: 1 }) .should.equal(true);
- data .should.eql({ foo: 1 }); // no coercion
- _ajv.validate(schema, data = { foo: true }) .should.equal(true);
- data .should.eql({ foo: true }); // no coercion
- _ajv.validate(schema, data = { foo: null }) .should.equal(true);
- data .should.eql({ foo: null }); // no coercion
- _ajv.validate(schema, data = { foo: 'abc' }) .should.equal(false);
- data .should.eql({ foo: 'abc' }); // can't coerce
- _ajv.validate(schema, data = { foo: {} }) .should.equal(false);
- data .should.eql({ foo: {} }); // can't coerce
- _ajv.validate(schema, data = { foo: [] }) .should.equal(false);
- data .should.eql({ foo: [] }); // can't coerce
- });
- });
- it('should coerce to multiple types in order with integer type', function() {
- var schema = {
- type: 'object',
- properties: {
- foo: {
- type: [ 'integer', 'boolean', 'null' ]
- }
- }
- };
- instances.forEach(function (_ajv) {
- var data;
- _ajv.validate(schema, data = { foo: '1' }) .should.equal(true);
- data .should.eql({ foo: 1 });
- _ajv.validate(schema, data = { foo: 'false' }) .should.equal(true);
- data .should.eql({ foo: false });
- _ajv.validate(schema, data = { foo: 1 }) .should.equal(true);
- data .should.eql({ foo: 1 }); // no coercion
- _ajv.validate(schema, data = { foo: true }) .should.equal(true);
- data .should.eql({ foo: true }); // no coercion
- _ajv.validate(schema, data = { foo: null }) .should.equal(true);
- data .should.eql({ foo: null }); // no coercion
- _ajv.validate(schema, data = { foo: 'abc' }) .should.equal(false);
- data .should.eql({ foo: 'abc' }); // can't coerce
- _ajv.validate(schema, data = { foo: {} }) .should.equal(false);
- data .should.eql({ foo: {} }); // can't coerce
- _ajv.validate(schema, data = { foo: [] }) .should.equal(false);
- data .should.eql({ foo: [] }); // can't coerce
- });
- });
- it('should fail to coerce non-number if multiple properties/items are coerced (issue #152)', function() {
- var schema = {
- type: 'object',
- properties: {
- foo: { type: 'number' },
- bar: { type: 'number' }
- }
- };
- var schema2 = {
- type: 'array',
- items: { type: 'number' }
- };
- instances.forEach(function (_ajv) {
- var data = { foo: '123', bar: 'bar' };
- _ajv.validate(schema, data) .should.equal(false);
- data .should.eql({ foo: 123, bar: 'bar' });
- var data2 = [ '123', 'bar' ];
- _ajv.validate(schema2, data2) .should.equal(false);
- data2 .should.eql([ 123, 'bar' ]);
- });
- });
- it('should update data if the schema is in ref that is not inlined', function () {
- instances.push(new Ajv({ coerceTypes: true, inlineRefs: false }));
- var schema = {
- type: 'object',
- definitions: {
- foo: { type: 'number' }
- },
- properties: {
- foo: { $ref: '#/definitions/foo' }
- }
- };
- var schema2 = {
- type: 'object',
- definitions: {
- foo: {
- // allOf is needed to make sure that "foo" is compiled to a separate function
- // and not simply passed through (as it would be if it were only $ref)
- allOf: [{ $ref: '#/definitions/bar' }]
- },
- bar: { type: 'number' }
- },
- properties: {
- foo: { $ref: '#/definitions/foo' }
- }
- };
- var schemaRecursive = {
- type: [ 'object', 'number' ],
- properties: {
- foo: { $ref: '#' }
- }
- };
- var schemaRecursive2 = {
- $id: 'http://e.com/schema.json#',
- definitions: {
- foo: {
- $id: 'http://e.com/foo.json#',
- type: [ 'object', 'number' ],
- properties: {
- foo: { $ref: '#' }
- }
- }
- },
- properties: {
- foo: { $ref: 'http://e.com/foo.json#' }
- }
- };
- instances.forEach(function (_ajv) {
- testCoercion(schema, { foo: '1' }, { foo: 1 });
- testCoercion(schema2, { foo: '1' }, { foo: 1 });
- testCoercion(schemaRecursive, { foo: { foo: '1' } }, { foo: { foo: 1 } });
- testCoercion(schemaRecursive2, { foo: { foo: { foo: '1' } } },
- { foo: { foo: { foo: 1 } } });
- function testCoercion(_schema, fromData, toData) {
- var valid = _ajv.validate(_schema, fromData);
- // if (!valid) console.log(schema, fromData, toData);
- valid. should.equal(true);
- fromData .should.eql(toData);
- }
- });
- });
- it('should generate one error for type with coerceTypes option (issue #469)', function() {
- var schema = {
- "type": "number",
- "minimum": 10
- };
- instances.forEach(function (_ajv) {
- var validate = _ajv.compile(schema);
- validate(9). should.equal(false);
- validate.errors.length .should.equal(1);
- validate(11). should.equal(true);
- validate('foo'). should.equal(false);
- validate.errors.length .should.equal(1);
- });
- });
- it('should check "uniqueItems" after coercion', function() {
- var schema = {
- items: {type: 'number'},
- uniqueItems: true
- };
- instances.forEach(function (_ajv) {
- var validate = _ajv.compile(schema);
- validate([1, '2', 3]). should.equal(true);
- validate([1, '2', 2]). should.equal(false);
- validate.errors.length .should.equal(1);
- validate.errors[0].keyword .should.equal('uniqueItems');
- });
- });
- it('should check "contains" after coercion', function() {
- var schema = {
- items: {type: 'number'},
- contains: {const: 2}
- };
- instances.forEach(function (_ajv) {
- var validate = _ajv.compile(schema);
- validate([1, '2', 3]). should.equal(true);
- validate([1, '3', 4]). should.equal(false);
- validate.errors.pop().keyword .should.equal('contains');
- });
- });
- function testRules(rules, cb) {
- for (var toType in rules) {
- for (var fromType in rules[toType]) {
- var tests = rules[toType][fromType];
- tests.forEach(function (test) {
- var canCoerce = test.to !== undefined;
- var schema = canCoerce
- ? (Array.isArray(test.to)
- ? { "type": toType, "items": { "type": fromType, "enum": [ test.to[0] ] } }
- : { "type": toType, "enum": [ test.to ] })
- : { type: toType };
- cb(test, schema, canCoerce, toType, fromType);
- });
- }
- }
- }
- });
|