joi 14.1.0 → 14.3.1
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/README.md +2 -2
- package/lib/errors.js +9 -7
- package/lib/index.js +21 -4
- package/lib/language.js +2 -0
- package/lib/types/any/index.js +16 -2
- package/lib/types/any/settings.js +2 -2
- package/lib/types/array/index.js +45 -2
- package/lib/types/object/index.js +10 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Imagine you run facebook and you want visitors to sign up on the website with re
|
|
|
16
16
|
This is joi, joi allows you to create *blueprints* or *schemas* for JavaScript objects (an object that stores information) to ensure *validation* of key information.
|
|
17
17
|
|
|
18
18
|
# API
|
|
19
|
-
See the detailed [API Reference](https://github.com/hapijs/joi/blob/v14.1
|
|
19
|
+
See the detailed [API Reference](https://github.com/hapijs/joi/blob/v14.3.1/API.md).
|
|
20
20
|
|
|
21
21
|
# Example
|
|
22
22
|
|
|
@@ -115,7 +115,7 @@ When validating a schema:
|
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
* Strings are utf-8 encoded by default.
|
|
118
|
-
* Rules are defined in an additive fashion and evaluated in order
|
|
118
|
+
* Rules are defined in an additive fashion and evaluated in order, first the inclusive rules, then the exclusive rules.
|
|
119
119
|
|
|
120
120
|
# Browsers
|
|
121
121
|
|
package/lib/errors.js
CHANGED
|
@@ -106,7 +106,7 @@ exports.Err = class {
|
|
|
106
106
|
return childrenString;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
const hasKey =
|
|
109
|
+
const hasKey = /{{!?label}}/.test(format);
|
|
110
110
|
const skipKey = format.length > 2 && format[0] === '!' && format[1] === '!';
|
|
111
111
|
|
|
112
112
|
if (skipKey) {
|
|
@@ -123,7 +123,7 @@ exports.Err = class {
|
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
const message = format.replace(
|
|
126
|
+
const message = format.replace(/{{(!?)([^}]+)}}/g, ($0, isSecure, name) => {
|
|
127
127
|
|
|
128
128
|
const value = Hoek.reach(this.context, name);
|
|
129
129
|
const normalized = internals.stringify(value, wrapArrays);
|
|
@@ -165,7 +165,9 @@ exports.process = function (errors, object) {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
if (item.flags.error && typeof item.flags.error !== 'function') {
|
|
168
|
-
|
|
168
|
+
if (!item.flags.selfError || !item.context.reason) {
|
|
169
|
+
return item.flags.error;
|
|
170
|
+
}
|
|
169
171
|
}
|
|
170
172
|
|
|
171
173
|
let itemMessage;
|
|
@@ -344,10 +346,10 @@ internals.annotate = function (stripColorCodes) {
|
|
|
344
346
|
}
|
|
345
347
|
|
|
346
348
|
const replacers = {
|
|
347
|
-
key: /_\$key\$_([, \d]+)_\$end\$_
|
|
348
|
-
missing:
|
|
349
|
-
arrayIndex: /\s
|
|
350
|
-
specials: /"\[(NaN|Symbol.*|-?Infinity|function.*|\(.*)
|
|
349
|
+
key: /_\$key\$_([, \d]+)_\$end\$_"/g,
|
|
350
|
+
missing: /"_\$miss\$_([^|]+)\|(\d+)_\$end\$_": "__missing__"/g,
|
|
351
|
+
arrayIndex: /\s*"_\$idx\$_([, \d]+)_\$end\$_",?\n(.*)/g,
|
|
352
|
+
specials: /"\[(NaN|Symbol.*|-?Infinity|function.*|\(.*)]"/g
|
|
351
353
|
};
|
|
352
354
|
|
|
353
355
|
let message = internals.safeStringify(obj, 2)
|
package/lib/index.js
CHANGED
|
@@ -46,6 +46,7 @@ internals.root = function () {
|
|
|
46
46
|
const root = any.clone();
|
|
47
47
|
Any.prototype._currentJoi = root;
|
|
48
48
|
root._currentJoi = root;
|
|
49
|
+
root._binds = new Set(['any', 'alternatives', 'alt', 'array', 'boolean', 'binary', 'date', 'func', 'number', 'object', 'string', 'symbol', 'validate', 'describe', 'compile', 'assert', 'attempt', 'lazy', 'defaults', 'extend', 'allow', 'valid', 'only', 'equal', 'invalid', 'disallow', 'not', 'required', 'exist', 'optional', 'forbidden', 'strip', 'when', 'empty', 'default']);
|
|
49
50
|
|
|
50
51
|
root.any = function (...args) {
|
|
51
52
|
|
|
@@ -141,14 +142,14 @@ internals.root = function () {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
const options = count === 2 ? args[1] : undefined;
|
|
144
|
-
const schema =
|
|
145
|
+
const schema = this.compile(args[0]);
|
|
145
146
|
|
|
146
147
|
return schema._validateWithOptions(value, options, callback);
|
|
147
148
|
};
|
|
148
149
|
|
|
149
150
|
root.describe = function (...args) {
|
|
150
151
|
|
|
151
|
-
const schema = args.length ?
|
|
152
|
+
const schema = args.length ? this.compile(args[0]) : any;
|
|
152
153
|
return schema.describe();
|
|
153
154
|
};
|
|
154
155
|
|
|
@@ -168,12 +169,12 @@ internals.root = function () {
|
|
|
168
169
|
|
|
169
170
|
root.assert = function (value, schema, message) {
|
|
170
171
|
|
|
171
|
-
|
|
172
|
+
this.attempt(value, schema, message);
|
|
172
173
|
};
|
|
173
174
|
|
|
174
175
|
root.attempt = function (value, schema, message) {
|
|
175
176
|
|
|
176
|
-
const result =
|
|
177
|
+
const result = this.validate(value, schema);
|
|
177
178
|
const error = result.error;
|
|
178
179
|
if (error) {
|
|
179
180
|
if (!message) {
|
|
@@ -259,6 +260,18 @@ internals.root = function () {
|
|
|
259
260
|
return joi;
|
|
260
261
|
};
|
|
261
262
|
|
|
263
|
+
root.bind = function () {
|
|
264
|
+
|
|
265
|
+
const joi = Object.create(this);
|
|
266
|
+
|
|
267
|
+
joi._binds.forEach((bind) => {
|
|
268
|
+
|
|
269
|
+
joi[bind] = joi[bind].bind(joi);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return joi;
|
|
273
|
+
};
|
|
274
|
+
|
|
262
275
|
root.extend = function (...args) {
|
|
263
276
|
|
|
264
277
|
const extensions = Hoek.flatten(args);
|
|
@@ -268,6 +281,8 @@ internals.root = function () {
|
|
|
268
281
|
|
|
269
282
|
const joi = Object.create(this.any());
|
|
270
283
|
Object.assign(joi, this);
|
|
284
|
+
joi._currentJoi = joi;
|
|
285
|
+
joi._binds = new Set(joi._binds);
|
|
271
286
|
|
|
272
287
|
for (let i = 0; i < extensions.length; ++i) {
|
|
273
288
|
let extension = extensions[i];
|
|
@@ -416,6 +431,8 @@ internals.root = function () {
|
|
|
416
431
|
|
|
417
432
|
return internals.callWithDefaults.call(this, instance, extArgs);
|
|
418
433
|
};
|
|
434
|
+
|
|
435
|
+
joi._binds.add(extension.name);
|
|
419
436
|
}
|
|
420
437
|
|
|
421
438
|
return joi;
|
package/lib/language.js
CHANGED
|
@@ -37,6 +37,8 @@ exports.errors = {
|
|
|
37
37
|
includesRequiredBoth: 'does not contain {{knownMisses}} and {{unknownMisses}} other required value(s)',
|
|
38
38
|
excludes: 'at position {{pos}} contains an excluded value',
|
|
39
39
|
excludesSingle: 'single value of "{{!label}}" contains an excluded value',
|
|
40
|
+
hasKnown: 'does not contain at least one required match for type "{{!patternLabel}}"',
|
|
41
|
+
hasUnknown: 'does not contain at least one required match',
|
|
40
42
|
min: 'must contain at least {{limit}} items',
|
|
41
43
|
max: 'must contain less than or equal to {{limit}} items',
|
|
42
44
|
length: 'must contain {{limit}} items',
|
package/lib/types/any/index.js
CHANGED
|
@@ -279,12 +279,20 @@ module.exports = internals.Any = class {
|
|
|
279
279
|
return obj;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
error(err) {
|
|
282
|
+
error(err, options = { self: false }) {
|
|
283
283
|
|
|
284
284
|
Hoek.assert(err && (err instanceof Error || typeof err === 'function'), 'Must provide a valid Error object or a function');
|
|
285
285
|
|
|
286
|
+
const unknownKeys = Object.keys(options).filter((k) => !['self'].includes(k));
|
|
287
|
+
Hoek.assert(unknownKeys.length === 0, `Options ${unknownKeys} are unknown`);
|
|
288
|
+
|
|
286
289
|
const obj = this.clone();
|
|
287
290
|
obj._flags.error = err;
|
|
291
|
+
|
|
292
|
+
if (options.self) {
|
|
293
|
+
obj._flags.selfError = true;
|
|
294
|
+
}
|
|
295
|
+
|
|
288
296
|
return obj;
|
|
289
297
|
}
|
|
290
298
|
|
|
@@ -715,7 +723,13 @@ module.exports = internals.Any = class {
|
|
|
715
723
|
finalValue = Hoek.clone(this._flags.default);
|
|
716
724
|
}
|
|
717
725
|
|
|
718
|
-
if (errors.length &&
|
|
726
|
+
if (errors.length &&
|
|
727
|
+
typeof this._flags.error === 'function' &&
|
|
728
|
+
(
|
|
729
|
+
!this._flags.selfError ||
|
|
730
|
+
errors.some((e) => state.path.length === e.path.length)
|
|
731
|
+
)
|
|
732
|
+
) {
|
|
719
733
|
const change = this._flags.error.call(this, errors);
|
|
720
734
|
|
|
721
735
|
if (typeof change === 'string') {
|
|
@@ -24,8 +24,8 @@ exports.concat = function (target, source) {
|
|
|
24
24
|
|
|
25
25
|
Object.assign(obj, source);
|
|
26
26
|
|
|
27
|
-
if (language) {
|
|
28
|
-
obj.language = Hoek.applyToDefaults(
|
|
27
|
+
if (language && target && target.language) {
|
|
28
|
+
obj.language = Hoek.applyToDefaults(target.language, language);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if (obj[Symbols.settingsCache]) {
|
package/lib/types/array/index.js
CHANGED
|
@@ -332,6 +332,15 @@ internals.Array = class extends Any {
|
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
if (description.rules) {
|
|
336
|
+
for (let i = 0; i < description.rules.length; ++i) {
|
|
337
|
+
const rule = description.rules[i];
|
|
338
|
+
if (rule.name === 'has') {
|
|
339
|
+
rule.arg = rule.arg.describe();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
335
344
|
return description;
|
|
336
345
|
}
|
|
337
346
|
|
|
@@ -352,7 +361,7 @@ internals.Array = class extends Any {
|
|
|
352
361
|
castErr.path = index;
|
|
353
362
|
}
|
|
354
363
|
|
|
355
|
-
castErr.message = castErr.message
|
|
364
|
+
castErr.message = `${castErr.message}(${castErr.path})`;
|
|
356
365
|
throw castErr;
|
|
357
366
|
}
|
|
358
367
|
|
|
@@ -389,7 +398,7 @@ internals.Array = class extends Any {
|
|
|
389
398
|
castErr.path = index;
|
|
390
399
|
}
|
|
391
400
|
|
|
392
|
-
castErr.message = castErr.message
|
|
401
|
+
castErr.message = `${castErr.message}(${castErr.path})`;
|
|
393
402
|
throw castErr;
|
|
394
403
|
}
|
|
395
404
|
|
|
@@ -483,6 +492,40 @@ internals.Array = class extends Any {
|
|
|
483
492
|
});
|
|
484
493
|
}
|
|
485
494
|
|
|
495
|
+
has(schema) {
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
schema = Cast.schema(this._currentJoi, schema);
|
|
499
|
+
}
|
|
500
|
+
catch (castErr) {
|
|
501
|
+
if (castErr.hasOwnProperty('path')) {
|
|
502
|
+
castErr.message = `${castErr.message}(${castErr.path})`;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
throw castErr;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return this._test('has', schema, function (value, state, options) {
|
|
509
|
+
|
|
510
|
+
const isValid = value.some((item, idx) => {
|
|
511
|
+
|
|
512
|
+
const localState = new State(idx, [...state.path, idx], state.key, state.reference);
|
|
513
|
+
return !schema._validate(item, localState, options).errors;
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
if (isValid) {
|
|
517
|
+
return value;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const patternLabel = schema._getLabel();
|
|
521
|
+
if (patternLabel) {
|
|
522
|
+
return this.createError('array.hasKnown', { patternLabel }, state, options);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return this.createError('array.hasUnknown', null, state, options);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
486
529
|
unique(comparator, configs) {
|
|
487
530
|
|
|
488
531
|
Hoek.assert(comparator === undefined ||
|
|
@@ -308,7 +308,7 @@ internals.Object = class extends Any {
|
|
|
308
308
|
const hasKey = dep.key !== null;
|
|
309
309
|
const splitKey = hasKey && dep.key.split('.');
|
|
310
310
|
const localState = hasKey ? new State(splitKey[splitKey.length - 1], [...state.path, ...splitKey]) : new State(null, state.path);
|
|
311
|
-
const err = internals[dep.type].call(this, dep.key, hasKey && Hoek.reach(target, dep.key), dep.peers, target, localState, options);
|
|
311
|
+
const err = internals[dep.type].call(this, dep.key, hasKey && Hoek.reach(target, dep.key, { functions: true }), dep.peers, target, localState, options);
|
|
312
312
|
if (err instanceof Errors.Err) {
|
|
313
313
|
errors.push(err);
|
|
314
314
|
if (options.abortEarly) {
|
|
@@ -454,7 +454,7 @@ internals.Object = class extends Any {
|
|
|
454
454
|
}
|
|
455
455
|
catch (castErr) {
|
|
456
456
|
if (castErr.hasOwnProperty('path')) {
|
|
457
|
-
castErr.message = castErr.message
|
|
457
|
+
castErr.message = `${castErr.message}(${castErr.path})`;
|
|
458
458
|
}
|
|
459
459
|
|
|
460
460
|
throw castErr;
|
|
@@ -686,7 +686,7 @@ internals.Object = class extends Any {
|
|
|
686
686
|
}
|
|
687
687
|
catch (castErr) {
|
|
688
688
|
if (castErr.hasOwnProperty('path')) {
|
|
689
|
-
castErr.message = castErr.message
|
|
689
|
+
castErr.message = `${castErr.message}(${castErr.path})`;
|
|
690
690
|
}
|
|
691
691
|
|
|
692
692
|
throw castErr;
|
|
@@ -793,7 +793,7 @@ internals.with = function (key, value, peers, parent, state, options) {
|
|
|
793
793
|
for (let i = 0; i < peers.length; ++i) {
|
|
794
794
|
|
|
795
795
|
const peer = peers[i];
|
|
796
|
-
const keysExist = Hoek.reach(parent, peer);
|
|
796
|
+
const keysExist = Hoek.reach(parent, peer, { functions: true });
|
|
797
797
|
if (keysExist === undefined) {
|
|
798
798
|
|
|
799
799
|
return this.createError('object.with', {
|
|
@@ -815,7 +815,7 @@ internals.without = function (key, value, peers, parent, state, options) {
|
|
|
815
815
|
|
|
816
816
|
for (let i = 0; i < peers.length; ++i) {
|
|
817
817
|
const peer = peers[i];
|
|
818
|
-
const keysExist = Hoek.reach(parent, peer);
|
|
818
|
+
const keysExist = Hoek.reach(parent, peer, { functions: true });
|
|
819
819
|
if (keysExist !== undefined) {
|
|
820
820
|
|
|
821
821
|
return this.createError('object.without', {
|
|
@@ -834,7 +834,7 @@ internals.xor = function (key, value, peers, parent, state, options) {
|
|
|
834
834
|
const present = [];
|
|
835
835
|
for (let i = 0; i < peers.length; ++i) {
|
|
836
836
|
const peer = peers[i];
|
|
837
|
-
const keysExist = Hoek.reach(parent, peer);
|
|
837
|
+
const keysExist = Hoek.reach(parent, peer, { functions: true });
|
|
838
838
|
if (keysExist !== undefined) {
|
|
839
839
|
present.push(peer);
|
|
840
840
|
}
|
|
@@ -862,7 +862,7 @@ internals.oxor = function (key, value, peers, parent, state, options) {
|
|
|
862
862
|
const present = [];
|
|
863
863
|
for (let i = 0; i < peers.length; ++i) {
|
|
864
864
|
const peer = peers[i];
|
|
865
|
-
const keysExist = Hoek.reach(parent, peer);
|
|
865
|
+
const keysExist = Hoek.reach(parent, peer, { functions: true });
|
|
866
866
|
if (keysExist !== undefined) {
|
|
867
867
|
present.push(peer);
|
|
868
868
|
}
|
|
@@ -886,7 +886,7 @@ internals.or = function (key, value, peers, parent, state, options) {
|
|
|
886
886
|
|
|
887
887
|
for (let i = 0; i < peers.length; ++i) {
|
|
888
888
|
const peer = peers[i];
|
|
889
|
-
const keysExist = Hoek.reach(parent, peer);
|
|
889
|
+
const keysExist = Hoek.reach(parent, peer, { functions: true });
|
|
890
890
|
if (keysExist !== undefined) {
|
|
891
891
|
return;
|
|
892
892
|
}
|
|
@@ -906,7 +906,7 @@ internals.and = function (key, value, peers, parent, state, options) {
|
|
|
906
906
|
const count = peers.length;
|
|
907
907
|
for (let i = 0; i < count; ++i) {
|
|
908
908
|
const peer = peers[i];
|
|
909
|
-
const keysExist = Hoek.reach(parent, peer);
|
|
909
|
+
const keysExist = Hoek.reach(parent, peer, { functions: true });
|
|
910
910
|
if (keysExist === undefined) {
|
|
911
911
|
|
|
912
912
|
missing.push(peer);
|
|
@@ -935,7 +935,7 @@ internals.nand = function (key, value, peers, parent, state, options) {
|
|
|
935
935
|
const present = [];
|
|
936
936
|
for (let i = 0; i < peers.length; ++i) {
|
|
937
937
|
const peer = peers[i];
|
|
938
|
-
const keysExist = Hoek.reach(parent, peer);
|
|
938
|
+
const keysExist = Hoek.reach(parent, peer, { functions: true });
|
|
939
939
|
if (keysExist !== undefined) {
|
|
940
940
|
|
|
941
941
|
present.push(peer);
|