feat: keyword "nullable", #486, closes epoberezkin/ajv-keywords#32

master
Evgeny Poberezkin 2018-11-10 21:03:51 +00:00
parent 494026e44c
commit f2010f40f2
5 changed files with 102 additions and 1 deletions

View File

@ -1029,6 +1029,7 @@ Defaults:
jsonPointers: false,
uniqueItems: true,
unicode: true,
nullable: false,
format: 'fast',
formats: {},
unknownFormats: true,
@ -1075,6 +1076,7 @@ Defaults:
- _jsonPointers_: set `dataPath` property of errors using [JSON Pointers](https://tools.ietf.org/html/rfc6901) instead of JavaScript property access notation.
- _uniqueItems_: validate `uniqueItems` keyword (true by default).
- _unicode_: calculate correct length of strings with unicode pairs (true by default). Pass `false` to use `.length` of strings that is faster, but gives "incorrect" lengths of strings with unicode pairs - each unicode pair is counted as two characters.
- _nullable_: support keyword "nullable" from [Open API 3 specification](https://swagger.io/docs/specification/data-models/data-types/).
- _format_: formats validation mode ('fast' by default). Pass 'full' for more correct and slow validation or `false` not to validate formats at all. E.g., 25:00:00 and 2015/14/33 will be invalid time and date in 'full' mode but it will be valid in 'fast' mode.
- _formats_: an object with custom formats. Keys and values will be passed to `addFormat` method.
- _unknownFormats_: handling of unknown formats. Option values:

3
lib/ajv.d.ts vendored
View File

@ -163,7 +163,8 @@ declare namespace ajv {
sourceCode?: boolean;
processCode?: (code: string) => string;
cache?: object;
logger?: CustomLogger | false
logger?: CustomLogger | false;
nullable?: boolean;
}
type FormatValidator = string | RegExp | ((data: string) => boolean | PromiseLike<any>);

View File

@ -72,6 +72,7 @@ function Ajv(opts) {
if (opts.formats) addInitialFormats(this);
addDraft6MetaSchema(this);
if (typeof opts.meta == 'object') this.addMetaSchema(opts.meta);
if (opts.nullable) this.addKeyword('nullable', {metaSchema: {const: true}});
addInitialSchemas(this);
}

View File

@ -100,6 +100,16 @@
var $typeSchema = it.schema.type
, $typeIsArray = Array.isArray($typeSchema);
if ($typeSchema && it.opts.nullable && it.schema.nullable === true) {
if ($typeIsArray) {
if ($typeSchema.indexOf('null') == -1)
$typeSchema = $typeSchema.concat('null');
} else if ($typeSchema != 'null') {
$typeSchema = [$typeSchema, 'null'];
$typeIsArray = true;
}
}
if ($typeIsArray && $typeSchema.length == 1) {
$typeSchema = $typeSchema[0];
$typeIsArray = false;

View File

@ -1412,4 +1412,91 @@ describe('Ajv Options', function () {
}).should.throw(Error, /logger must implement log, warn and error methods/);
});
});
describe('nullable', function() {
var ajv;
describe('= true', function() {
beforeEach(function () {
ajv = new Ajv({
nullable: true
});
});
it('should add keyword "nullable"', function() {
testNullable({
type: 'number',
nullable: true
});
testNullable({
type: ['number'],
nullable: true
});
testNullable({
type: ['number', 'null']
});
testNullable({
type: ['number', 'null'],
nullable: true
});
testNotNullable({type: 'number'});
testNotNullable({type: ['number']});
});
it('"nullable" keyword must be "true" if present', function() {
should.throw(function() {
ajv.compile({nullable: false});
});
});
});
describe('without option "nullable"', function() {
it('should ignore keyword nullable', function() {
ajv = new Ajv;
testNotNullable({
type: 'number',
nullable: true
});
testNotNullable({
type: ['number'],
nullable: true
});
testNullable({
type: ['number', 'null'],
});
testNullable({
type: ['number', 'null'],
nullable: true
});
should.not.throw(function () {
ajv.compile({nullable: false});
});
});
});
function testNullable(schema) {
var validate = ajv.compile(schema);
validate(1) .should.equal(true);
validate(null) .should.equal(true);
validate('1') .should.equal(false);
}
function testNotNullable(schema) {
var validate = ajv.compile(schema);
validate(1) .should.equal(true);
validate(null) .should.equal(false);
validate('1') .should.equal(false);
}
});
});