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 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.0/API.md).
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 after whitelist and blacklist checks.
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 = /\{\{\!?label\}\}/.test(format);
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(/\{\{(\!?)([^}]+)\}\}/g, ($0, isSecure, name) => {
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
- return item.flags.error;
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\$_\"/g,
348
- missing: /\"_\$miss\$_([^\|]+)\|(\d+)_\$end\$_\"\: \"__missing__\"/g,
349
- arrayIndex: /\s*\"_\$idx\$_([, \d]+)_\$end\$_\",?\n(.*)/g,
350
- specials: /"\[(NaN|Symbol.*|-?Infinity|function.*|\(.*)\]"/g
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 = root.compile(args[0]);
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 ? root.compile(args[0]) : any;
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
- root.attempt(value, schema, message);
172
+ this.attempt(value, schema, message);
172
173
  };
173
174
 
174
175
  root.attempt = function (value, schema, message) {
175
176
 
176
- const result = root.validate(value, schema);
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',
@@ -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 && typeof this._flags.error === 'function') {
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(obj.language, language);
27
+ if (language && target && target.language) {
28
+ obj.language = Hoek.applyToDefaults(target.language, language);
29
29
  }
30
30
 
31
31
  if (obj[Symbols.settingsCache]) {
@@ -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 + '(' + castErr.path + ')';
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 + '(' + castErr.path + ')';
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 + '(' + castErr.path + ')';
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 + '(' + castErr.path + ')';
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);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "joi",
3
3
  "description": "Object schema validation",
4
- "version": "14.1.0",
4
+ "version": "14.3.1",
5
5
  "homepage": "https://github.com/hapijs/joi",
6
6
  "repository": "git://github.com/hapijs/joi",
7
7
  "main": "lib/index.js",