joi 8.3.0 → 9.0.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -0
- package/.github/CONTRIBUTING.md +14 -0
- package/.github/ISSUE_TEMPLATE.md +19 -0
- package/.npmignore +21 -0
- package/.travis.yml +8 -0
- package/API.md +1690 -0
- package/README.md +1 -1
- package/examples/conditionalRequire.js +52 -0
- package/examples/customMessage.js +31 -0
- package/examples/multipleWhen.js +26 -0
- package/examples/timestamps.js +22 -0
- package/generate-readme-toc.js +49 -0
- package/images/joi.png +0 -0
- package/images/validation.png +0 -0
- package/lib/any.js +65 -45
- package/lib/array.js +2 -6
- package/lib/boolean.js +9 -2
- package/lib/errors.js +2 -13
- package/lib/index.js +142 -3
- package/lib/language.js +2 -5
- package/lib/object.js +15 -6
- package/lib/string.js +0 -17
- package/package.json +6 -7
- package/test/alternatives.js +490 -0
- package/test/any.js +1751 -0
- package/test/array.js +1049 -0
- package/test/binary.js +190 -0
- package/test/boolean.js +163 -0
- package/test/date.js +441 -0
- package/test/errors.js +512 -0
- package/test/function.js +224 -0
- package/test/helper.js +65 -0
- package/test/index.js +2450 -0
- package/test/number.js +836 -0
- package/test/object.js +1318 -0
- package/test/ref.js +470 -0
- package/test/string.js +3206 -0
- package/lib/lazy.js +0 -51
package/README.md
CHANGED
|
@@ -95,4 +95,4 @@ When validating a schema:
|
|
|
95
95
|
* Rules are defined in an additive fashion and evaluated in order after whitelist and blacklist checks.
|
|
96
96
|
|
|
97
97
|
# API
|
|
98
|
-
See the [API Reference](https://github.com/hapijs/joi/blob/
|
|
98
|
+
See the [API Reference](https://github.com/hapijs/joi/blob/v9.0.0-0/API.md).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Load modules
|
|
4
|
+
|
|
5
|
+
const Joi = require('../');
|
|
6
|
+
|
|
7
|
+
// Delcare internals
|
|
8
|
+
|
|
9
|
+
const internals = {};
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// This is an example of a survey to obtain the reputation of Parisians
|
|
13
|
+
// It contains examples of how to conditionally require keys based on values of other keys
|
|
14
|
+
|
|
15
|
+
// This is a valid value for integer rating 1 - 5
|
|
16
|
+
const intRating = Joi.number().integer().min(1).max(5);
|
|
17
|
+
|
|
18
|
+
const schema = Joi.object().keys({
|
|
19
|
+
// Do you know any French people? yes or no (required)
|
|
20
|
+
q1: Joi.boolean().required(),
|
|
21
|
+
// Do you know any Parisians? yes or no (required if answered yes in q1)
|
|
22
|
+
q2: Joi.boolean()
|
|
23
|
+
.when('q1', { is: true, then: Joi.required() }),
|
|
24
|
+
// How many french in paris do you know? 1-6, 6-10, 11-50 or 50+ (required if answered yes in q2)
|
|
25
|
+
q3: Joi.string()
|
|
26
|
+
.when('q2', { is: true, then: Joi.valid('1-5', '6-10', '11-50', '50+').required() }),
|
|
27
|
+
// Rate 20% of most friendly Parisians, from how many people you know answered in q3, individually on 1-5 rating
|
|
28
|
+
q4: Joi.array()
|
|
29
|
+
.when('q3', {is: '1-5', then: Joi.array().min(0).max(1).items(intRating).required() })
|
|
30
|
+
.when('q3', {is: '6-10', then: Joi.array().min(1).max(2).items(intRating).required() })
|
|
31
|
+
.when('q3', {is: '11-50', then: Joi.array().min(2).max(10).items(intRating).required() })
|
|
32
|
+
.when('q3', {is: '50+', then: Joi.array().min(10).items(intRating).required() }),
|
|
33
|
+
// Rate remaining 80% of Parisians, from how many people you know answered in q3, individually on 1-5 rating
|
|
34
|
+
q5: Joi.array()
|
|
35
|
+
.when('q3', {is: '1-5', then: Joi.array().min(1).max(4).items(intRating).required() })
|
|
36
|
+
.when('q3', {is: '6-10', then: Joi.array().min(4).max(8).items(intRating).required() })
|
|
37
|
+
.when('q3', {is: '11-50', then: Joi.array().min(8).max(40).items(intRating).required() })
|
|
38
|
+
.when('q3', {is: '50+', then: Joi.array().min(40).items(intRating).required().required() }),
|
|
39
|
+
// Rate the reputation of Parisians in general, 1-5 rating
|
|
40
|
+
q6: intRating.required()
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const response = {
|
|
44
|
+
q1: true,
|
|
45
|
+
q2: true,
|
|
46
|
+
q3: '1-5',
|
|
47
|
+
q4: [5],
|
|
48
|
+
q5: [1],
|
|
49
|
+
q6: 2
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
Joi.assert(response, schema);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Load modules
|
|
4
|
+
|
|
5
|
+
const Joi = require('../');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// Declare internals
|
|
9
|
+
|
|
10
|
+
const internals = {};
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const schema = Joi.object().options({ abortEarly: false }).keys({
|
|
14
|
+
email: Joi.string().email().required().label('User Email'),
|
|
15
|
+
password: Joi.string().min(8).required(),
|
|
16
|
+
password_confirmation: Joi.any().valid(Joi.ref('password')).required().options({ language: { any: { allowOnly: 'must match password' }, label: 'Password Confirmation' } }).label('This label is not used because language.label takes precedence'),
|
|
17
|
+
first_name: Joi.string().required(),
|
|
18
|
+
last_name: Joi.string().required(),
|
|
19
|
+
company: Joi.string().optional()
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const data = {
|
|
24
|
+
email: 'not_a_valid_email_to_show_custom_label',
|
|
25
|
+
password: 'abcd1234',
|
|
26
|
+
password_confirmation: 'abc1',
|
|
27
|
+
first_name: 'Joe',
|
|
28
|
+
last_name: 'Doe'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
Joi.assert(data, schema);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Load modules
|
|
4
|
+
|
|
5
|
+
const Joi = require('../');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// Declare internals
|
|
9
|
+
|
|
10
|
+
const internals = {};
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const schema = {
|
|
14
|
+
type: Joi.string().required(),
|
|
15
|
+
subtype: Joi.alternatives()
|
|
16
|
+
.when('type', {is: 'video', then: Joi.valid('mp4', 'wav')})
|
|
17
|
+
.when('type', {is: 'audio', then: Joi.valid('mp3')})
|
|
18
|
+
.when('type', {is: 'image', then: Joi.valid('jpg', 'png')})
|
|
19
|
+
.when('type', {is: 'pdf' , then: Joi.valid('document')})
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
Joi.assert({ type: 'video', subtype: 'mp4' }, schema); // Pass
|
|
24
|
+
Joi.assert({ type: 'video', subtype: 'wav' }, schema); // Pass
|
|
25
|
+
Joi.assert({ type: 'other', subtype: 'something' }, schema); // Fail
|
|
26
|
+
Joi.assert({ type: 'audio', subtype: 'mp4' }, schema); // Fail
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Load modules
|
|
4
|
+
const Joi = require('../');
|
|
5
|
+
|
|
6
|
+
const now = new Date();
|
|
7
|
+
const javascriptTimestamp = now.getTime();
|
|
8
|
+
const unixTimestamp = now.getTime() / 1000;
|
|
9
|
+
|
|
10
|
+
const schema = Joi.object().options({ abortEarly: false }).keys({
|
|
11
|
+
javascript1: Joi.date().timestamp(),
|
|
12
|
+
javascript2: Joi.date().timestamp('javascript'),
|
|
13
|
+
unix: Joi.date().timestamp('unix')
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const data = {
|
|
17
|
+
javascript1: javascriptTimestamp,
|
|
18
|
+
javascript2: javascriptTimestamp,
|
|
19
|
+
unix: unixTimestamp
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
Joi.assert(data, schema);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Load modules
|
|
4
|
+
|
|
5
|
+
const Toc = require('markdown-toc');
|
|
6
|
+
const Fs = require('fs');
|
|
7
|
+
const Package = require('./package.json');
|
|
8
|
+
|
|
9
|
+
// Declare internals
|
|
10
|
+
|
|
11
|
+
const internals = {
|
|
12
|
+
api: {
|
|
13
|
+
filename: './API.md',
|
|
14
|
+
contents: Fs.readFileSync('./API.md', 'utf8')
|
|
15
|
+
},
|
|
16
|
+
readme: {
|
|
17
|
+
filename: './README.md',
|
|
18
|
+
contents: Fs.readFileSync('./README.md', 'utf8')
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
internals.generateToc = function () {
|
|
23
|
+
|
|
24
|
+
const tocOptions = {
|
|
25
|
+
bullets: '-',
|
|
26
|
+
slugify: function (text) {
|
|
27
|
+
|
|
28
|
+
return text.toLowerCase()
|
|
29
|
+
.replace(/\s/g, '-')
|
|
30
|
+
.replace(/[^\w-]/g, '');
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const api = Toc.insert(internals.api.contents, tocOptions)
|
|
35
|
+
.replace(/<!-- version -->(.|\n)*<!-- versionstop -->/, `<!-- version -->\n# ${Package.version} API Reference\n<!-- versionstop -->`);
|
|
36
|
+
|
|
37
|
+
Fs.writeFileSync(internals.api.filename, api);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
internals.generateLink = function () {
|
|
41
|
+
// create absolute URL for versioned docs
|
|
42
|
+
const readme = internals.readme.contents
|
|
43
|
+
.replace(/\[API Reference\]\(.*\)/gi, `[API Reference](${Package.homepage || ''}/blob/v${Package.version}/${internals.api.filename.substr(2)})`);
|
|
44
|
+
|
|
45
|
+
Fs.writeFileSync(internals.readme.filename, readme);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
internals.generateToc();
|
|
49
|
+
internals.generateLink();
|
package/images/joi.png
ADDED
|
Binary file
|
|
Binary file
|
package/lib/any.js
CHANGED
|
@@ -32,26 +32,37 @@ internals.defaults = {
|
|
|
32
32
|
|
|
33
33
|
internals.checkOptions = function (options) {
|
|
34
34
|
|
|
35
|
-
const
|
|
35
|
+
const optionType = {
|
|
36
|
+
abortEarly: 'boolean',
|
|
37
|
+
convert: 'boolean',
|
|
38
|
+
allowUnknown: 'boolean',
|
|
39
|
+
skipFunctions: 'boolean',
|
|
40
|
+
stripUnknown: 'boolean',
|
|
41
|
+
language: 'object',
|
|
42
|
+
presence: ['string', 'required', 'optional', 'forbidden', 'ignore'],
|
|
43
|
+
raw: 'boolean',
|
|
44
|
+
context: 'object',
|
|
45
|
+
strip: 'boolean',
|
|
46
|
+
noDefaults: 'boolean'
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const keys = Object.keys(options);
|
|
50
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
51
|
+
const key = keys[i];
|
|
52
|
+
const opt = optionType[key];
|
|
53
|
+
let type = opt;
|
|
54
|
+
let values = null;
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
skipFunctions: Joi.boolean(),
|
|
42
|
-
stripUnknown: [Joi.boolean(), Joi.object({ arrays: Joi.boolean(), objects: Joi.boolean() }).or('arrays', 'objects')],
|
|
43
|
-
language: Joi.object(),
|
|
44
|
-
presence: Joi.string().only('required', 'optional', 'forbidden', 'ignore'),
|
|
45
|
-
raw: Joi.boolean(),
|
|
46
|
-
context: Joi.object(),
|
|
47
|
-
strip: Joi.boolean(),
|
|
48
|
-
noDefaults: Joi.boolean(),
|
|
49
|
-
error: Joi.object()
|
|
50
|
-
}).strict();
|
|
56
|
+
if (Array.isArray(opt)) {
|
|
57
|
+
type = opt[0];
|
|
58
|
+
values = opt.slice(1);
|
|
59
|
+
}
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
Hoek.assert(type, 'unknown key ' + key);
|
|
62
|
+
Hoek.assert(typeof options[key] === type, key + ' should be of type ' + type);
|
|
63
|
+
if (values) {
|
|
64
|
+
Hoek.assert(values.indexOf(options[key]) >= 0, key + ' should be one of ' + values.join(', '));
|
|
65
|
+
}
|
|
55
66
|
}
|
|
56
67
|
};
|
|
57
68
|
|
|
@@ -67,8 +78,7 @@ module.exports = internals.Any = function () {
|
|
|
67
78
|
this._invalids = new internals.Set();
|
|
68
79
|
this._tests = [];
|
|
69
80
|
this._refs = [];
|
|
70
|
-
this._flags = {
|
|
71
|
-
/*
|
|
81
|
+
this._flags = { /*
|
|
72
82
|
presence: 'optional', // optional, required, forbidden, ignore
|
|
73
83
|
allowOnly: false,
|
|
74
84
|
allowUnknown: undefined,
|
|
@@ -80,8 +90,7 @@ module.exports = internals.Any = function () {
|
|
|
80
90
|
case: undefined, // upper, lower
|
|
81
91
|
empty: undefined,
|
|
82
92
|
func: false
|
|
83
|
-
*/
|
|
84
|
-
};
|
|
93
|
+
*/ };
|
|
85
94
|
|
|
86
95
|
this._description = null;
|
|
87
96
|
this._unit = null;
|
|
@@ -208,10 +217,10 @@ internals.Any.prototype.concat = function (schema) {
|
|
|
208
217
|
};
|
|
209
218
|
|
|
210
219
|
|
|
211
|
-
internals.Any.prototype._test = function (name, arg, func) {
|
|
220
|
+
internals.Any.prototype._test = function (name, arg, func, options) {
|
|
212
221
|
|
|
213
222
|
const obj = this.clone();
|
|
214
|
-
obj._tests.push({ func, name, arg });
|
|
223
|
+
obj._tests.push({ func, name, arg, options });
|
|
215
224
|
return obj;
|
|
216
225
|
};
|
|
217
226
|
|
|
@@ -245,17 +254,6 @@ internals.Any.prototype.raw = function (isRaw) {
|
|
|
245
254
|
};
|
|
246
255
|
|
|
247
256
|
|
|
248
|
-
internals.Any.prototype.error = function (err) {
|
|
249
|
-
|
|
250
|
-
Hoek.assert(err && err instanceof Error, 'Must provide a valid Error object');
|
|
251
|
-
|
|
252
|
-
const obj = this.clone();
|
|
253
|
-
obj._settings = obj._settings || {};
|
|
254
|
-
obj._settings.error = err;
|
|
255
|
-
return obj;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
|
|
259
257
|
internals.Any.prototype._allow = function () {
|
|
260
258
|
|
|
261
259
|
const values = Hoek.flatten(Array.prototype.slice.call(arguments));
|
|
@@ -394,7 +392,7 @@ internals.Any.prototype.when = function (ref, options) {
|
|
|
394
392
|
Alternatives = Alternatives || require('./alternatives');
|
|
395
393
|
const obj = Alternatives.when(ref, { is: options.is, then, otherwise });
|
|
396
394
|
obj._flags.presence = 'ignore';
|
|
397
|
-
obj._settings = internals.concatSettings(
|
|
395
|
+
obj._settings = internals.concatSettings({ baseType: this }, obj._settings);
|
|
398
396
|
|
|
399
397
|
return obj;
|
|
400
398
|
};
|
|
@@ -625,13 +623,16 @@ internals.Any.prototype._validate = function (value, state, options, reference)
|
|
|
625
623
|
|
|
626
624
|
for (let i = 0; i < this._tests.length; ++i) {
|
|
627
625
|
const test = this._tests[i];
|
|
628
|
-
const
|
|
629
|
-
if (
|
|
630
|
-
errors.push(
|
|
626
|
+
const ret = test.func.call(this, value, state, options);
|
|
627
|
+
if (ret && ret.isJoi) {
|
|
628
|
+
errors.push(ret);
|
|
631
629
|
if (options.abortEarly) {
|
|
632
630
|
return finish();
|
|
633
631
|
}
|
|
634
632
|
}
|
|
633
|
+
else if (ret !== null) {
|
|
634
|
+
value = ret;
|
|
635
|
+
}
|
|
635
636
|
}
|
|
636
637
|
|
|
637
638
|
return finish();
|
|
@@ -677,7 +678,7 @@ internals.Any.prototype.describe = function () {
|
|
|
677
678
|
|
|
678
679
|
const flags = Object.keys(this._flags);
|
|
679
680
|
if (flags.length) {
|
|
680
|
-
if (this._flags.hasOwnProperty('empty') || this._flags.hasOwnProperty('default')
|
|
681
|
+
if (this._flags.hasOwnProperty('empty') || this._flags.hasOwnProperty('default')) {
|
|
681
682
|
description.flags = {};
|
|
682
683
|
for (let i = 0; i < flags.length; ++i) {
|
|
683
684
|
const flag = flags[i];
|
|
@@ -695,9 +696,6 @@ internals.Any.prototype.describe = function () {
|
|
|
695
696
|
description.flags[flag] = this._flags[flag];
|
|
696
697
|
}
|
|
697
698
|
}
|
|
698
|
-
else if (flag === 'lazy') {
|
|
699
|
-
// We don't want it in the description
|
|
700
|
-
}
|
|
701
699
|
else {
|
|
702
700
|
description.flags[flag] = this._flags[flag];
|
|
703
701
|
}
|
|
@@ -753,9 +751,31 @@ internals.Any.prototype.describe = function () {
|
|
|
753
751
|
for (let i = 0; i < this._tests.length; ++i) {
|
|
754
752
|
const validator = this._tests[i];
|
|
755
753
|
const item = { name: validator.name };
|
|
754
|
+
|
|
756
755
|
if (validator.arg !== void 0) {
|
|
757
756
|
item.arg = Ref.isRef(validator.arg) ? validator.arg.toString() : validator.arg;
|
|
758
757
|
}
|
|
758
|
+
|
|
759
|
+
const options = validator.options;
|
|
760
|
+
if (options) {
|
|
761
|
+
if (options.hasRef) {
|
|
762
|
+
item.arg = {};
|
|
763
|
+
const keys = Object.keys(validator.arg);
|
|
764
|
+
for (let j = 0; j < keys.length; ++j) {
|
|
765
|
+
const key = keys[j];
|
|
766
|
+
const value = validator.arg[key];
|
|
767
|
+
item.arg[key] = Ref.isRef(value) ? value.toString() : value;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (typeof options.description === 'string') {
|
|
772
|
+
item.description = options.description;
|
|
773
|
+
}
|
|
774
|
+
else if (typeof options.description === 'function') {
|
|
775
|
+
item.description = options.description(item.arg);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
759
779
|
description.rules.push(item);
|
|
760
780
|
}
|
|
761
781
|
|
|
@@ -771,7 +791,6 @@ internals.Any.prototype.describe = function () {
|
|
|
771
791
|
return description;
|
|
772
792
|
};
|
|
773
793
|
|
|
774
|
-
|
|
775
794
|
internals.Any.prototype.label = function (name) {
|
|
776
795
|
|
|
777
796
|
Hoek.assert(name && typeof name === 'string', 'Label name must be a non-empty string');
|
|
@@ -779,7 +798,8 @@ internals.Any.prototype.label = function (name) {
|
|
|
779
798
|
const obj = this.clone();
|
|
780
799
|
const options = { language: { label: name } };
|
|
781
800
|
|
|
782
|
-
|
|
801
|
+
// If language.label is set, it should override this label
|
|
802
|
+
obj._settings = internals.concatSettings(options, obj._settings);
|
|
783
803
|
return obj;
|
|
784
804
|
};
|
|
785
805
|
|
package/lib/array.js
CHANGED
|
@@ -206,10 +206,6 @@ internals.checkItems = function (items, wasArray, state, options) {
|
|
|
206
206
|
|
|
207
207
|
// Inclusions
|
|
208
208
|
|
|
209
|
-
const stripUnknown = options.stripUnknown
|
|
210
|
-
? (options.stripUnknown === true ? true : !!options.stripUnknown.arrays)
|
|
211
|
-
: false;
|
|
212
|
-
|
|
213
209
|
jl = inclusions.length;
|
|
214
210
|
for (let j = 0; j < jl; ++j) {
|
|
215
211
|
const inclusion = inclusions[j];
|
|
@@ -238,7 +234,7 @@ internals.checkItems = function (items, wasArray, state, options) {
|
|
|
238
234
|
|
|
239
235
|
// Return the actual error if only one inclusion defined
|
|
240
236
|
if (jl === 1) {
|
|
241
|
-
if (stripUnknown) {
|
|
237
|
+
if (options.stripUnknown) {
|
|
242
238
|
internals.fastSplice(items, i);
|
|
243
239
|
--i;
|
|
244
240
|
--il;
|
|
@@ -262,7 +258,7 @@ internals.checkItems = function (items, wasArray, state, options) {
|
|
|
262
258
|
}
|
|
263
259
|
|
|
264
260
|
if (this._inner.inclusions.length && !isValid) {
|
|
265
|
-
if (stripUnknown) {
|
|
261
|
+
if (options.stripUnknown) {
|
|
266
262
|
internals.fastSplice(items, i);
|
|
267
263
|
--i;
|
|
268
264
|
--il;
|
package/lib/boolean.js
CHANGED
|
@@ -30,8 +30,15 @@ internals.Boolean.prototype._base = function (value, state, options) {
|
|
|
30
30
|
options.convert) {
|
|
31
31
|
|
|
32
32
|
const lower = value.toLowerCase();
|
|
33
|
-
result.value = (lower === 'true' || lower === 'yes' || lower === 'on' ? true
|
|
34
|
-
|
|
33
|
+
result.value = (lower === 'true' || lower === 'yes' || lower === 'on' || lower === '1' ? true
|
|
34
|
+
: (lower === 'false' || lower === 'no' || lower === 'off' || lower === '0' ? false : value));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof value === 'number' &&
|
|
38
|
+
options.convert) {
|
|
39
|
+
|
|
40
|
+
result.value = (value === 1 ? true
|
|
41
|
+
: (value === 0 ? false : value));
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
result.errors = (typeof result.value === 'boolean') ? null : this.createError('boolean.base', null, state, options);
|
package/lib/errors.js
CHANGED
|
@@ -115,10 +115,6 @@ exports.process = function (errors, object) {
|
|
|
115
115
|
for (let i = 0; i < localErrors.length; ++i) {
|
|
116
116
|
const item = localErrors[i];
|
|
117
117
|
|
|
118
|
-
if (item.options.error) {
|
|
119
|
-
return item.options.error;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
118
|
const detail = {
|
|
123
119
|
message: item.toString(),
|
|
124
120
|
path: internals.getPath(item),
|
|
@@ -131,12 +127,8 @@ exports.process = function (errors, object) {
|
|
|
131
127
|
}
|
|
132
128
|
|
|
133
129
|
// Do not push intermediate errors, we're only interested in leafs
|
|
134
|
-
|
|
135
130
|
if (item.context.reason && item.context.reason.length) {
|
|
136
|
-
|
|
137
|
-
if (override) {
|
|
138
|
-
return override;
|
|
139
|
-
}
|
|
131
|
+
processErrors(item.context.reason, item.path);
|
|
140
132
|
}
|
|
141
133
|
else {
|
|
142
134
|
details.push(detail);
|
|
@@ -144,10 +136,7 @@ exports.process = function (errors, object) {
|
|
|
144
136
|
}
|
|
145
137
|
};
|
|
146
138
|
|
|
147
|
-
|
|
148
|
-
if (override) {
|
|
149
|
-
return override;
|
|
150
|
-
}
|
|
139
|
+
processErrors(errors);
|
|
151
140
|
|
|
152
141
|
const error = new Error(message);
|
|
153
142
|
error.isJoi = true;
|
package/lib/index.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
const Hoek = require('hoek');
|
|
6
6
|
const Any = require('./any');
|
|
7
7
|
const Cast = require('./cast');
|
|
8
|
-
const Lazy = require('./lazy');
|
|
9
8
|
const Ref = require('./ref');
|
|
10
9
|
|
|
11
10
|
|
|
@@ -173,13 +172,153 @@ internals.root = function () {
|
|
|
173
172
|
}
|
|
174
173
|
};
|
|
175
174
|
|
|
176
|
-
root.
|
|
175
|
+
root.extend = function () {
|
|
177
176
|
|
|
178
|
-
|
|
177
|
+
const extensions = Hoek.flatten(Array.prototype.slice.call(arguments));
|
|
178
|
+
Hoek.assert(extensions.length > 0, 'You need to provide at least one extension');
|
|
179
|
+
|
|
180
|
+
this.assert(extensions, internals.extensionsSchema);
|
|
181
|
+
|
|
182
|
+
const joi = Object.assign({}, this);
|
|
183
|
+
|
|
184
|
+
for (let i = 0; i < extensions.length; ++i) {
|
|
185
|
+
const extension = extensions[i];
|
|
186
|
+
const base = (extension.base || this.any()).clone(); // Cloning because we're going to override language afterwards
|
|
187
|
+
const ctor = base.constructor;
|
|
188
|
+
const type = function () { // eslint-disable-line no-loop-func
|
|
189
|
+
|
|
190
|
+
ctor.call(this);
|
|
191
|
+
|
|
192
|
+
if (extension.base) {
|
|
193
|
+
Object.assign(this, base);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this._type = extension.name;
|
|
197
|
+
|
|
198
|
+
if (extension.language) {
|
|
199
|
+
this._settings = this._settings || { language: {} };
|
|
200
|
+
this._settings.language = Hoek.applyToDefaults(this._settings.language, {
|
|
201
|
+
[extension.name]: extension.language
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
Hoek.inherits(type, ctor);
|
|
206
|
+
|
|
207
|
+
if (extension.pre) {
|
|
208
|
+
type.prototype._base = function (value, state, options) {
|
|
209
|
+
|
|
210
|
+
if (ctor.prototype._base) {
|
|
211
|
+
const baseRet = ctor.prototype._base.call(this, value, state, options);
|
|
212
|
+
|
|
213
|
+
if (baseRet.errors) {
|
|
214
|
+
return baseRet;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
value = baseRet.value;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const ret = extension.pre.call(this, value, state, options);
|
|
221
|
+
if (ret && ret.isJoi) {
|
|
222
|
+
return { value, errors: ret };
|
|
223
|
+
}
|
|
224
|
+
else if (ret !== null) {
|
|
225
|
+
return { value: ret };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return { value };
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (extension.rules) {
|
|
233
|
+
for (let j = 0; j < extension.rules.length; ++j) {
|
|
234
|
+
const rule = extension.rules[j];
|
|
235
|
+
const ruleArgs = rule.params ?
|
|
236
|
+
(rule.params instanceof Any ? rule.params._inner.children.map((k) => k.key) : Object.keys(rule.params)) :
|
|
237
|
+
[];
|
|
238
|
+
const validateArgs = rule.params ? Cast.schema(rule.params) : null;
|
|
239
|
+
|
|
240
|
+
type.prototype[rule.name] = function () { // eslint-disable-line no-loop-func
|
|
241
|
+
|
|
242
|
+
if (arguments.length > ruleArgs.length) {
|
|
243
|
+
throw new Error('Unexpected number of arguments');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const args = Array.prototype.slice.call(arguments);
|
|
247
|
+
let hasRef = false;
|
|
248
|
+
const arg = {};
|
|
249
|
+
|
|
250
|
+
for (let k = 0; k < ruleArgs.length; ++k) {
|
|
251
|
+
arg[ruleArgs[k]] = args[k];
|
|
252
|
+
if (!hasRef && Ref.isRef(args[k])) {
|
|
253
|
+
hasRef = true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (validateArgs) {
|
|
258
|
+
joi.assert(arg, validateArgs);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let schema;
|
|
262
|
+
if (rule.validate) {
|
|
263
|
+
const validate = rule.validate.bind(this, arg);
|
|
264
|
+
|
|
265
|
+
schema = this._test(rule.name, arg, validate, {
|
|
266
|
+
description: rule.description,
|
|
267
|
+
hasRef
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
schema = this.clone();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (rule.setup) {
|
|
275
|
+
rule.setup.call(schema, arg);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return schema;
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (extension.describe) {
|
|
284
|
+
type.prototype.describe = function () {
|
|
285
|
+
|
|
286
|
+
const description = ctor.prototype.describe.call(this);
|
|
287
|
+
return extension.describe.call(this, description);
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const instance = new type();
|
|
292
|
+
joi[extension.name] = function () {
|
|
293
|
+
|
|
294
|
+
return instance;
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return joi;
|
|
179
299
|
};
|
|
180
300
|
|
|
181
301
|
return root;
|
|
182
302
|
};
|
|
183
303
|
|
|
184
304
|
|
|
305
|
+
internals.extensionsSchema = internals.array.items(internals.object.keys({
|
|
306
|
+
base: internals.object.type(Any, 'Joi object'),
|
|
307
|
+
name: internals.string.required(),
|
|
308
|
+
pre: internals.object._func().arity(3),
|
|
309
|
+
language: internals.object,
|
|
310
|
+
describe: internals.object._func().arity(1),
|
|
311
|
+
rules: internals.array.items(internals.object.keys({
|
|
312
|
+
name: internals.string.required(),
|
|
313
|
+
setup: internals.object._func().arity(1),
|
|
314
|
+
validate: internals.object._func().arity(4),
|
|
315
|
+
params: [
|
|
316
|
+
internals.object.pattern(/.*/, internals.object.type(Any, 'Joi object')),
|
|
317
|
+
internals.object.type(internals.object.constructor, 'Joi object')
|
|
318
|
+
],
|
|
319
|
+
description: [internals.string, internals.object._func().arity(1)]
|
|
320
|
+
}).or('setup', 'validate'))
|
|
321
|
+
})).strict();
|
|
322
|
+
|
|
323
|
+
|
|
185
324
|
module.exports = internals.root();
|
package/lib/language.js
CHANGED
|
@@ -68,11 +68,8 @@ exports.errors = {
|
|
|
68
68
|
base: 'must be a Function',
|
|
69
69
|
arity: 'must have an arity of {{n}}',
|
|
70
70
|
minArity: 'must have an arity greater or equal to {{n}}',
|
|
71
|
-
maxArity: 'must have an arity lesser or equal to {{n}}'
|
|
72
|
-
|
|
73
|
-
lazy: {
|
|
74
|
-
base: '!!schema error: lazy schema must be set',
|
|
75
|
-
schema: '!!schema error: lazy schema function must return a schema'
|
|
71
|
+
maxArity: 'must have an arity lesser or equal to {{n}}',
|
|
72
|
+
ref: 'must be a Joi reference'
|
|
76
73
|
},
|
|
77
74
|
object: {
|
|
78
75
|
base: 'must be an object',
|