Skip to content

Commit

Permalink
feat: invalidDefaults option to warn when defaults are ignored, fixes #…
Browse files Browse the repository at this point in the history
  • Loading branch information
not-an-aardvark committed Mar 1, 2019
1 parent 2aa49ae commit c081061
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 14 deletions.
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -798,13 +798,14 @@ console.log(validate(data)); // true
console.log(data); // [ 1, "foo" ]
```

`default` keywords in other cases are ignored:
`default` keywords in other cases are invalid:

- not in `properties` or `items` subschemas
- in schemas inside `anyOf`, `oneOf` and `not` (see [#42](https://github.com/epoberezkin/ajv/issues/42))
- in `if` subschema of `switch` keyword
- in schemas generated by custom macro keywords

The [`invalidDefaults` option](#options) customizes Ajv's behavior for invalid defaults (`false` ignores invalid defaults, `true` raises an error, and `"log"` outputs a warning).

## Coercing data types

Expand Down Expand Up @@ -1070,6 +1071,7 @@ Defaults:
removeAdditional: false,
useDefaults: false,
coerceTypes: false,
invalidDefaults: false,
// asynchronous validation options:
transpile: undefined, // requires ajv-async package
// advanced options:
Expand Down Expand Up @@ -1151,6 +1153,10 @@ Defaults:
- `false` (default) - no type coercion.
- `true` - coerce scalar data types.
- `"array"` - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema).
- _invalidDefaults_: specify behavior for invalid `default` keywords in schemas. Option values:
- `false` (default) - ignore invalid defaults
- `true` - if an invalid default is present, throw an error
- `"log"` - if an invalid default is present, log warning


##### Asynchronous validation options
Expand Down
1 change: 1 addition & 0 deletions lib/ajv.d.ts
Expand Up @@ -180,6 +180,7 @@ declare namespace ajv {
removeAdditional?: boolean | 'all' | 'failing';
useDefaults?: boolean | 'shared';
coerceTypes?: boolean | 'array';
invalidDefaults?: boolean | 'log';
async?: boolean | string;
transpile?: string | ((code: string) => string);
meta?: boolean | object;
Expand Down
2 changes: 1 addition & 1 deletion lib/ajv.js
Expand Up @@ -39,7 +39,7 @@ Ajv.$dataMetaSchema = $dataMetaSchema;

var META_SCHEMA_ID = 'http://json-schema.org/draft-07/schema';

var META_IGNORE_OPTIONS = [ 'removeAdditional', 'useDefaults', 'coerceTypes' ];
var META_IGNORE_OPTIONS = [ 'removeAdditional', 'useDefaults', 'coerceTypes', 'invalidDefaults' ];
var META_SUPPORT_DATA = ['/properties'];

/**
Expand Down
30 changes: 20 additions & 10 deletions lib/dot/defaults.def
@@ -1,15 +1,25 @@
{{## def.assignDefault:
if ({{=$passData}} === undefined
{{? it.opts.useDefaults == 'empty' }}
|| {{=$passData}} === null
|| {{=$passData}} === ''
{{? it.compositeRule }}
{{? it.opts.invalidDefaults }}
{{? it.opts.invalidDefaults === 'log' }}
{{ it.logger.warn('default is ignored for: ' + $passData); }}
{{??}}
{{ throw new Error('default is ignored for: ' + $passData); }}
{{?}}
{{?}}
)
{{=$passData}} = {{? it.opts.useDefaults == 'shared' }}
{{= it.useDefault($sch.default) }}
{{??}}
{{= JSON.stringify($sch.default) }}
{{?}};
{{??}}
if ({{=$passData}} === undefined
{{? it.opts.useDefaults == 'empty' }}
|| {{=$passData}} === null
|| {{=$passData}} === ''
{{?}}
)
{{=$passData}} = {{? it.opts.useDefaults == 'shared' }}
{{= it.useDefault($sch.default) }}
{{??}}
{{= JSON.stringify($sch.default) }}
{{?}};
{{?}}
#}}


Expand Down
9 changes: 8 additions & 1 deletion lib/dot/validate.jst
Expand Up @@ -72,6 +72,13 @@

it.dataPathArr = [undefined];
}}
{{? it.opts.invalidDefaults && it.schema.default !== undefined }}
{{? it.opts.invalidDefaults === 'log' }}
{{ it.logger.warn('default is ignored in the schema root'); }}
{{??}}
{{ throw new Error('default is ignored in the schema root'); }}
{{?}}
{{?}}

var vErrors = null; {{ /* don't edit, used in replace */ }}
var errors = 0; {{ /* don't edit, used in replace */ }}
Expand Down Expand Up @@ -177,7 +184,7 @@
{{? $rulesGroup.type }}
if ({{= it.util.checkDataType($rulesGroup.type, $data) }}) {
{{?}}
{{? it.opts.useDefaults && !it.compositeRule }}
{{? it.opts.useDefaults }}
{{? $rulesGroup.type == 'object' && it.schema.properties }}
{{# def.defaultProperties }}
{{?? $rulesGroup.type == 'array' && Array.isArray(it.schema.items) }}
Expand Down
88 changes: 87 additions & 1 deletion spec/options/useDefaults.spec.js
Expand Up @@ -2,7 +2,7 @@

var Ajv = require('../ajv');
var getAjvInstances = require('../ajv_instances');
require('../chai').should();
var should = require('../chai').should();


describe('useDefaults options', function() {
Expand Down Expand Up @@ -220,4 +220,90 @@ describe('useDefaults options', function() {
});
});
});

describe('invalidDefaults option', function() {
it('should throw an error given an invalid default in the schema root when invalidDefaults is true', function() {
var ajv = new Ajv({useDefaults: true, invalidDefaults: true});
var schema = {
default: 5,
properties: {}
};
should.throw(function() { ajv.compile(schema); });
});

it('should throw an error given an invalid default in oneOf when invalidDefaults is true', function() {
var ajv = new Ajv({useDefaults: true, invalidDefaults: true});
var schema = {
oneOf: [
{ enum: ['foo', 'bar'] },
{
properties: {
foo: {
default: true
}
}
}
]
};
should.throw(function() { ajv.compile(schema); });
});

it('should log a warning given an invalid default in the schema root when invalidDefaults is "log"', function() {
var warnArg = null;
var ajv = new Ajv({
useDefaults: true,
invalidDefaults: 'log',
logger: {
log: function() {
throw new Error('should not be called');
},
warn: function(warning) {
warnArg = warning;
},
error: function() {
throw new Error('should not be called');
}
}
});
var schema = {
default: 5,
properties: {}
};
ajv.compile(schema);
should.equal(warnArg, 'default is ignored in the schema root');
});

it('should log a warning given an invalid default in oneOf when invalidDefaults is "log"', function() {
var warnArg = null;
var ajv = new Ajv({
useDefaults: true,
invalidDefaults: 'log',
logger: {
log: function() {
throw new Error('should not be called');
},
warn: function(warning) {
warnArg = warning;
},
error: function() {
throw new Error('should not be called');
}
}
});
var schema = {
oneOf: [
{ enum: ['foo', 'bar'] },
{
properties: {
foo: {
default: true
}
}
}
]
};
ajv.compile(schema);
should.equal(warnArg, 'default is ignored for: data.foo');
});
});
});

0 comments on commit c081061

Please sign in to comment.