Skip to content

Commit 7ecec64

Browse files
committed
feat: "dynamicDefaults" keyword
1 parent a7c431d commit 7ecec64

File tree

5 files changed

+368
-7
lines changed

5 files changed

+368
-7
lines changed

README.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ Custom JSON-Schema keywords for [ajv](https://github.com/epoberezkin/ajv) valida
77
[![Coverage Status](https://coveralls.io/repos/github/epoberezkin/ajv-keywords/badge.svg?branch=master)](https://coveralls.io/github/epoberezkin/ajv-keywords?branch=master)
88

99

10+
## Contents
11+
12+
- [Install](#install)
13+
- [Usage](#usage)
14+
- [Keywords](#keywords)
15+
- [typeof](#typeof)
16+
- [instanceof](#instanceof)
17+
- [range and exclusiveRange](#range-and-exclusiverange)
18+
- [propertyNames](#propertynames)
19+
- [regexp](#regexp)
20+
- [dynamicDefaults](#dynamicdefaults)
21+
- [License](#license)
22+
23+
1024
## Install
1125

1226
```
@@ -168,6 +182,117 @@ var invalidData = {
168182
```
169183

170184

185+
### `dynamicDefaults`
186+
187+
This keyword allows to assign dynamic defaults to properties, such as timestamps, unique IDs etc.
188+
189+
This keyword only works if `useDefaults` options is used and not inside `anyOf` keywrods etc., in the same way as [default keyword treated by Ajv](https://github.com/epoberezkin/ajv#assigning-defaults).
190+
191+
The keyword should be added on the object level. Its value should be an object with each property corresponding to a property name, in the same way as in standard `properties` keyword. The value of each property can be:
192+
193+
- an identifier of default function (a string)
194+
- an object with properties `func` (an identifier) and `args` (an object with parameters that will be passed to this function during schema compilation - see examples).
195+
196+
The properties used in `dynamicDefaults` should not be added to `required` keyword (or validation will fail), because unlike `default` this keyword is processed after validation.
197+
198+
There are several predefined dynamic default functions:
199+
200+
- `"timestamp"` - current timestamp in milliseconds
201+
- `"datetime"' - current date and time as string (ISO, valid according to `date-time` format)
202+
- `"date"' - current date as string (ISO, valid according to `date` format)
203+
- `"time"` - current time as string (ISO, valid according to `time` format)
204+
- `"random"` - pseudo-random number in [0, 1) interval
205+
- `"randomint"` - pseudo-random integer number. If string is used as a property value, the function will randomly return 0 or 1. If object `{func: 'randomint', max: N}` is used then the default will be an integer number in [0, N) interval.
206+
- `"seq"` - sequential integer number starting from 0. If string is used as a property value, the default sequence will be used. If object `{func: 'seq', name: 'foo'}` is used then the sequence with name `"foo"` will be used. Sequences are global, even if different ajv instances are used.
207+
208+
```javascript
209+
var schema = {
210+
type: 'object',
211+
dynamicDefaults: {
212+
ts: 'datetime',
213+
r: { func: 'randomint', max: 100 },
214+
id: { func: 'seq', name: 'id' }
215+
},
216+
properties: {
217+
ts: {
218+
type: 'string',
219+
format: 'datetime'
220+
},
221+
r: {
222+
type: 'integer',
223+
minimum: 0,
224+
maximum: 100,
225+
exclusiveMaximum: true
226+
},
227+
id: {
228+
type: 'integer',
229+
minimum: 0
230+
}
231+
}
232+
};
233+
234+
var data = {};
235+
ajv.validate(data); // true
236+
data; // { ts: '2016-12-01T22:07:28.829Z', r: 25, id: 0 }
237+
238+
var data1 = {};
239+
ajv.validate(data1); // true
240+
data1; // { ts: '2016-12-01T22:07:29.832Z', r: 68, id: 1 }
241+
242+
ajv.validate(data1); // true
243+
data1; // didn't change, as all properties were defined
244+
```
245+
246+
You can add your own dynamic default function to be recognised by this keyword:
247+
248+
```javascript
249+
var uuid = require('uuid');
250+
251+
function uuidV4() { return uuid.v4(); }
252+
253+
var definition = require('ajv-keywords').get('dynamicDefaults').definition;
254+
// or require('ajv-keywords/keywords/dynamicDefaults').definition;
255+
definition.DEFAULTS.uuid = uuidV4;
256+
257+
var schema = {
258+
dynamicDefaults: { id: 'uuid' },
259+
properties: { id: { type: 'string', format: 'uuid' } }
260+
};
261+
262+
var data = {};
263+
ajv.validate(schema, data); // true
264+
data; // { id: 'a1183fbe-697b-4030-9bcc-cfeb282a9150' };
265+
266+
var data1 = {};
267+
ajv.validate(schema, data1); // true
268+
data1; // { id: '5b008de7-1669-467a-a5c6-70fa244d7209' }
269+
```
270+
271+
You also can define dynamic default that accepts parameters, e.g. version of uuid:
272+
273+
```javascript
274+
var uuid = require('uuid');
275+
276+
function getUuid(args) {
277+
var version = 'v' + (arvs && args.v || 4);
278+
return function() {
279+
return uuid[version]();
280+
};
281+
}
282+
283+
var definition = require('ajv-keywords').get('dynamicDefaults').definition;
284+
definition.DEFAULTS.uuid = getUuid;
285+
286+
var schema = {
287+
dynamicDefaults: {
288+
id1: 'uuid', // v4
289+
id2: { func: 'uuid', v: 4 }, // v4
290+
id3: { func: 'uuid', v: 1 } // v1
291+
}
292+
};
293+
```
294+
295+
171296
## License
172297

173298
[MIT](https://github.com/JSONScript/ajv-keywords/blob/master/LICENSE)

keywords/dynamicDefaults.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
3+
var sequences = {};
4+
5+
var DEFAULTS = {
6+
timestamp: function() { return Date.now(); },
7+
datetime: function() { return (new Date).toISOString(); },
8+
date: function() { return (new Date).toISOString().slice(0, 10); },
9+
time: function() { return (new Date).toISOString().slice(11); },
10+
random: function() { return Math.random(); },
11+
randomint: function (args) {
12+
var limit = args && args.max || 2;
13+
return function() { return Math.floor(Math.random() * limit); };
14+
},
15+
seq: function (args) {
16+
var name = args && args.name || '';
17+
sequences[name] = sequences[name] || 0;
18+
return function() { return sequences[name]++; };
19+
}
20+
};
21+
22+
module.exports = function defFunc(ajv) {
23+
defFunc.definition = {
24+
compile: function (schema, parentSchema, it) {
25+
var funcs = {};
26+
27+
for (var key in schema) {
28+
var d = schema[key];
29+
var func = getDefault(typeof d == 'string' ? d : d.func);
30+
funcs[key] = func.length ? func(d.args) : func;
31+
}
32+
33+
return it.opts.useDefaults && !it.compositeRule
34+
? assignDefaults
35+
: noop;
36+
37+
function assignDefaults(data) {
38+
for (var prop in schema)
39+
if (data[prop] === undefined) data[prop] = funcs[prop]();
40+
return true;
41+
}
42+
43+
function noop() { return true; }
44+
},
45+
DEFAULTS: DEFAULTS,
46+
metaSchema: {
47+
type: 'object',
48+
additionalProperties: {
49+
type: ['string', 'object'],
50+
additionalProperties: false,
51+
required: ['func', 'args'],
52+
properties: {
53+
func: { type: 'string' },
54+
args: { type: 'object' }
55+
}
56+
}
57+
}
58+
};
59+
60+
ajv.addKeyword('dynamicDefaults', defFunc.definition);
61+
62+
function getDefault(d) {
63+
var def = DEFAULTS[d];
64+
if (def) return def;
65+
throw new Error('invalid "dynamicDefaults" keyword property value: ' + d);
66+
}
67+
};

keywords/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
'use strict';
22

33
module.exports = {
4-
'typeof': require('./typeof'),
54
'instanceof': require('./instanceof'),
6-
range: require('./range'),
75
propertyNames: require('./propertyNames'),
8-
regexp: require('./regexp')
6+
range: require('./range'),
7+
regexp: require('./regexp'),
8+
'typeof': require('./typeof'),
9+
dynamicDefaults: require('./dynamicDefaults')
910
};

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ajv-keywords",
3-
"version": "1.1.1",
3+
"version": "1.2.0",
44
"description": "Custom JSON-Schema keywords for ajv validator",
55
"main": "index.js",
66
"scripts": {
@@ -25,16 +25,17 @@
2525
},
2626
"homepage": "https://github.com/epoberezkin/ajv-keywords#readme",
2727
"peerDependencies": {
28-
"ajv": ">=4.2.0"
28+
"ajv": ">=4.9.0"
2929
},
3030
"devDependencies": {
31-
"ajv": "^4.7.4",
31+
"ajv": "^4.9.0",
3232
"ajv-pack": "^0.2.0",
3333
"chai": "^3.5.0",
3434
"coveralls": "^2.11.9",
3535
"eslint": "^3.6.0",
3636
"istanbul": "^0.4.3",
3737
"mocha": "^3.0.2",
38-
"pre-commit": "^1.1.3"
38+
"pre-commit": "^1.1.3",
39+
"uuid": "^3.0.1"
3940
}
4041
}

0 commit comments

Comments
 (0)