joi 10.4.2 → 10.6.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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2016, Project contributors
1
+ Copyright (c) 2012-2017, Project contributors
2
2
  Copyright (c) 2012-2014, Walmart
3
3
  All rights reserved.
4
4
 
package/README.md CHANGED
@@ -125,7 +125,7 @@ When validating a schema:
125
125
  * Rules are defined in an additive fashion and evaluated in order after whitelist and blacklist checks.
126
126
 
127
127
  # API
128
- See the [API Reference](https://github.com/hapijs/joi/blob/v10.4.2/API.md).
128
+ See the [API Reference](https://github.com/hapijs/joi/blob/v10.6.0/API.md).
129
129
 
130
130
  # Browsers
131
131
 
package/lib/cast.js CHANGED
@@ -12,10 +12,10 @@ const Ref = require('./ref');
12
12
 
13
13
  const internals = {
14
14
  any: null,
15
- date: require('./date'),
16
- string: require('./string'),
17
- number: require('./number'),
18
- boolean: require('./boolean'),
15
+ date: require('./types/date'),
16
+ string: require('./types/string'),
17
+ number: require('./types/number'),
18
+ boolean: require('./types/boolean'),
19
19
  alt: null,
20
20
  object: null
21
21
  };
@@ -23,9 +23,9 @@ const internals = {
23
23
 
24
24
  exports.schema = function (config) {
25
25
 
26
- internals.any = internals.any || new (require('./any'))();
27
- internals.alt = internals.alt || require('./alternatives');
28
- internals.object = internals.object || require('./object');
26
+ internals.any = internals.any || new (require('./types/any'))();
27
+ internals.alt = internals.alt || require('./types/alternatives');
28
+ internals.object = internals.object || require('./types/object');
29
29
 
30
30
  if (config !== undefined && config !== null && typeof config === 'object') {
31
31
 
package/lib/errors.js CHANGED
@@ -301,11 +301,13 @@ internals.annotate = function (stripColorCodes) {
301
301
  const seg = path[j];
302
302
 
303
303
  if (ref.isImmutable) {
304
- // Joi schemas are not cloned by hoek, we have to take this extra step
305
- ref = ref.clone();
304
+ ref = ref.clone(); // joi schemas are not cloned by hoek, we have to take this extra step
306
305
  }
307
306
 
308
- if (j + 1 < path.length && typeof ref[seg] !== 'string') {
307
+ if (j + 1 < path.length &&
308
+ ref[seg] &&
309
+ typeof ref[seg] !== 'string') {
310
+
309
311
  ref = ref[seg];
310
312
  }
311
313
  else {
package/lib/index.js CHANGED
@@ -3,24 +3,24 @@
3
3
  // Load modules
4
4
 
5
5
  const Hoek = require('hoek');
6
- const Any = require('./any');
6
+ const Any = require('./types/any');
7
7
  const Cast = require('./cast');
8
8
  const Errors = require('./errors');
9
- const Lazy = require('./lazy');
9
+ const Lazy = require('./types/lazy');
10
10
  const Ref = require('./ref');
11
11
 
12
12
 
13
13
  // Declare internals
14
14
 
15
15
  const internals = {
16
- alternatives: require('./alternatives'),
17
- array: require('./array'),
18
- boolean: require('./boolean'),
19
- binary: require('./binary'),
20
- date: require('./date'),
21
- number: require('./number'),
22
- object: require('./object'),
23
- string: require('./string')
16
+ alternatives: require('./types/alternatives'),
17
+ array: require('./types/array'),
18
+ boolean: require('./types/boolean'),
19
+ binary: require('./types/binary'),
20
+ date: require('./types/date'),
21
+ number: require('./types/number'),
22
+ object: require('./types/object'),
23
+ string: require('./types/string')
24
24
  };
25
25
 
26
26
 
@@ -330,7 +330,11 @@ internals.root = function () {
330
330
  }
331
331
 
332
332
  if (rule.setup) {
333
- rule.setup.call(schema, arg);
333
+ const newSchema = rule.setup.call(schema, arg);
334
+ if (newSchema !== undefined) {
335
+ Hoek.assert(newSchema instanceof Any, `Setup of extension Joi.${this._type}().${rule.name}() must return undefined or a Joi object`);
336
+ schema = newSchema;
337
+ }
334
338
  }
335
339
 
336
340
  return schema;
package/lib/language.js CHANGED
@@ -42,6 +42,7 @@ exports.errors = {
42
42
  length: 'must contain {{limit}} items',
43
43
  ordered: 'at position {{pos}} fails because {{reason}}',
44
44
  orderedLength: 'at position {{pos}} fails because array must contain at most {{limit}} items',
45
+ ref: 'references "{{ref}}" which is not a positive integer',
45
46
  sparse: 'must not be a sparse array',
46
47
  unique: 'position {{pos}} contains a duplicate value'
47
48
  },
@@ -3,9 +3,9 @@
3
3
  // Load modules
4
4
 
5
5
  const Hoek = require('hoek');
6
- const Any = require('./any');
7
- const Cast = require('./cast');
8
- const Ref = require('./ref');
6
+ const Any = require('../any');
7
+ const Cast = require('../../cast');
8
+ const Ref = require('../../ref');
9
9
 
10
10
 
11
11
  // Declare internals
@@ -3,8 +3,8 @@
3
3
  // Load modules
4
4
 
5
5
  const Hoek = require('hoek');
6
- const Ref = require('./ref');
7
- const Errors = require('./errors');
6
+ const Ref = require('../../ref');
7
+ const Errors = require('../../errors');
8
8
  let Alternatives = null; // Delay-loaded to prevent circular dependencies
9
9
  let Cast = null;
10
10
 
@@ -12,7 +12,7 @@ let Cast = null;
12
12
  // Declare internals
13
13
 
14
14
  const internals = {
15
- Set: require('./set')
15
+ Set: require('../../set')
16
16
  };
17
17
 
18
18
 
@@ -35,7 +35,7 @@ module.exports = internals.Any = class {
35
35
 
36
36
  constructor() {
37
37
 
38
- Cast = Cast || require('./cast');
38
+ Cast = Cast || require('../../cast');
39
39
 
40
40
  this.isJoi = true;
41
41
  this._type = 'any';
@@ -83,7 +83,7 @@ module.exports = internals.Any = class {
83
83
 
84
84
  checkOptions(options) {
85
85
 
86
- const Schemas = require('./schemas');
86
+ const Schemas = require('../../schemas');
87
87
  const result = Schemas.options.validate(options);
88
88
  if (result.error) {
89
89
  throw new Error(result.error.details[0].message);
@@ -381,7 +381,7 @@ module.exports = internals.Any = class {
381
381
  const then = options.hasOwnProperty('then') ? this.concat(Cast.schema(options.then)) : undefined;
382
382
  const otherwise = options.hasOwnProperty('otherwise') ? this.concat(Cast.schema(options.otherwise)) : undefined;
383
383
 
384
- Alternatives = Alternatives || require('./alternatives');
384
+ Alternatives = Alternatives || require('../alternatives');
385
385
  const obj = Alternatives.when(ref, { is: options.is, then, otherwise });
386
386
  obj._flags.presence = 'ignore';
387
387
  obj._baseType = this;
@@ -2,8 +2,9 @@
2
2
 
3
3
  // Load modules
4
4
 
5
- const Any = require('./any');
6
- const Cast = require('./cast');
5
+ const Any = require('../any');
6
+ const Cast = require('../../cast');
7
+ const Ref = require('../../ref');
7
8
  const Hoek = require('hoek');
8
9
 
9
10
 
@@ -390,11 +391,25 @@ internals.Array = class extends Any {
390
391
 
391
392
  min(limit) {
392
393
 
393
- Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
394
+ const isRef = Ref.isRef(limit);
395
+
396
+ Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference');
394
397
 
395
398
  return this._test('min', limit, function (value, state, options) {
396
399
 
397
- if (value.length >= limit) {
400
+ let compareTo;
401
+ if (isRef) {
402
+ compareTo = limit(state.reference || state.parent, options);
403
+
404
+ if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) {
405
+ return this.createError('array.ref', { ref: limit.key }, state, options);
406
+ }
407
+ }
408
+ else {
409
+ compareTo = limit;
410
+ }
411
+
412
+ if (value.length >= compareTo) {
398
413
  return value;
399
414
  }
400
415
 
@@ -404,11 +419,25 @@ internals.Array = class extends Any {
404
419
 
405
420
  max(limit) {
406
421
 
407
- Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
422
+ const isRef = Ref.isRef(limit);
423
+
424
+ Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference');
408
425
 
409
426
  return this._test('max', limit, function (value, state, options) {
410
427
 
411
- if (value.length <= limit) {
428
+ let compareTo;
429
+ if (isRef) {
430
+ compareTo = limit(state.reference || state.parent, options);
431
+
432
+ if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) {
433
+ return this.createError('array.ref', { ref: limit.key }, state, options);
434
+ }
435
+ }
436
+ else {
437
+ compareTo = limit;
438
+ }
439
+
440
+ if (value.length <= compareTo) {
412
441
  return value;
413
442
  }
414
443
 
@@ -418,11 +447,25 @@ internals.Array = class extends Any {
418
447
 
419
448
  length(limit) {
420
449
 
421
- Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
450
+ const isRef = Ref.isRef(limit);
451
+
452
+ Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference');
422
453
 
423
454
  return this._test('length', limit, function (value, state, options) {
424
455
 
425
- if (value.length === limit) {
456
+ let compareTo;
457
+ if (isRef) {
458
+ compareTo = limit(state.reference || state.parent, options);
459
+
460
+ if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) {
461
+ return this.createError('array.ref', { ref: limit.key }, state, options);
462
+ }
463
+ }
464
+ else {
465
+ compareTo = limit;
466
+ }
467
+
468
+ if (value.length === compareTo) {
426
469
  return value;
427
470
  }
428
471
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  // Load modules
4
4
 
5
- const Any = require('./any');
5
+ const Any = require('../any');
6
6
  const Hoek = require('hoek');
7
7
 
8
8
 
@@ -2,14 +2,14 @@
2
2
 
3
3
  // Load modules
4
4
 
5
- const Any = require('./any');
5
+ const Any = require('../any');
6
6
  const Hoek = require('hoek');
7
7
 
8
8
 
9
9
  // Declare internals
10
10
 
11
11
  const internals = {
12
- Set: require('./set')
12
+ Set: require('../../set')
13
13
  };
14
14
 
15
15
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  // Load modules
4
4
 
5
- const Any = require('./any');
6
- const Ref = require('./ref');
5
+ const Any = require('../any');
6
+ const Ref = require('../../ref');
7
7
  const Hoek = require('hoek');
8
8
 
9
9
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  // Load modules
4
4
 
5
- const Any = require('./any');
5
+ const Any = require('../any');
6
6
  const Hoek = require('hoek');
7
7
 
8
8
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  // Load modules
4
4
 
5
- const Any = require('./any');
6
- const Ref = require('./ref');
5
+ const Any = require('../any');
6
+ const Ref = require('../../ref');
7
7
  const Hoek = require('hoek');
8
8
 
9
9
 
@@ -4,10 +4,10 @@
4
4
 
5
5
  const Hoek = require('hoek');
6
6
  const Topo = require('topo');
7
- const Any = require('./any');
8
- const Errors = require('./errors');
9
- const Cast = require('./cast');
10
- const Ref = require('./ref');
7
+ const Any = require('../any');
8
+ const Errors = require('../../errors');
9
+ const Cast = require('../../cast');
10
+ const Ref = require('../../ref');
11
11
 
12
12
 
13
13
  // Declare internals
@@ -504,6 +504,12 @@ internals.Object = class extends Any {
504
504
  return this.applyFunctionToChildren(children, 'optional');
505
505
  }
506
506
 
507
+ forbiddenKeys(children) {
508
+
509
+ children = Hoek.flatten(Array.prototype.slice.call(arguments));
510
+ return this.applyFunctionToChildren(children, 'forbidden');
511
+ }
512
+
507
513
  rename(from, to, options) {
508
514
 
509
515
  Hoek.assert(typeof from === 'string', 'Rename missing the from argument');
@@ -4,18 +4,28 @@
4
4
 
5
5
  const Net = require('net');
6
6
  const Hoek = require('hoek');
7
- const Isemail = require('isemail');
8
- const Any = require('./any');
9
- const Ref = require('./ref');
10
- const JoiDate = require('./date');
11
- const Uri = require('./string/uri');
12
- const Ip = require('./string/ip');
7
+ let Isemail; // Loaded on demand
8
+ const Any = require('../any');
9
+ const Ref = require('../../ref');
10
+ const JoiDate = require('../date');
11
+ const Uri = require('./uri');
12
+ const Ip = require('./ip');
13
13
 
14
14
  // Declare internals
15
15
 
16
16
  const internals = {
17
17
  uriRegex: Uri.createUriRegex(),
18
- ipRegex: Ip.createIpRegex(['ipv4', 'ipv6', 'ipvfuture'], 'optional')
18
+ ipRegex: Ip.createIpRegex(['ipv4', 'ipv6', 'ipvfuture'], 'optional'),
19
+ guidBrackets: {
20
+ '{': '}', '[': ']', '(': ')', '': ''
21
+ },
22
+ guidVersions: {
23
+ uuidv1: '1',
24
+ uuidv2: '2',
25
+ uuidv3: '3',
26
+ uuidv4: '4',
27
+ uuidv5: '5'
28
+ }
19
29
  };
20
30
 
21
31
  internals.String = class extends Any {
@@ -169,6 +179,8 @@ internals.String = class extends Any {
169
179
 
170
180
  return this._test('email', isEmailOptions, function (value, state, options) {
171
181
 
182
+ Isemail = Isemail || require('isemail');
183
+
172
184
  try {
173
185
  const result = Isemail.validate(value, isEmailOptions);
174
186
  if (result === true || result === 0) {
@@ -324,19 +336,7 @@ internals.String = class extends Any {
324
336
 
325
337
  guid(guidOptions) {
326
338
 
327
- const brackets = {
328
- '{': '}', '[': ']', '(': ')', '': ''
329
- };
330
-
331
- const uuids = {
332
- 'uuidv1': '1',
333
- 'uuidv2': '2',
334
- 'uuidv3': '3',
335
- 'uuidv4': '4',
336
- 'uuidv5': '5'
337
- };
338
-
339
- const versions = [];
339
+ let versionNumbers = '';
340
340
 
341
341
  if (guidOptions && guidOptions.version) {
342
342
  if (!Array.isArray(guidOptions.version)) {
@@ -344,50 +344,36 @@ internals.String = class extends Any {
344
344
  }
345
345
 
346
346
  Hoek.assert(guidOptions.version.length >= 1, 'version must have at least 1 valid version specified');
347
+ const versions = new Set();
347
348
 
348
349
  for (let i = 0; i < guidOptions.version.length; ++i) {
349
350
  let version = guidOptions.version[i];
350
351
  Hoek.assert(typeof version === 'string', 'version at position ' + i + ' must be a string');
351
352
  version = version.toLowerCase();
352
- Hoek.assert(uuids[version], 'version at position ' + i + ' must be one of ' + Object.keys(uuids).join(', '));
353
- Hoek.assert(versions.indexOf(version) === -1, 'version at position ' + i + ' must not be a duplicate.');
354
- versions.push(version);
353
+ const versionNumber = internals.guidVersions[version];
354
+ Hoek.assert(versionNumber, 'version at position ' + i + ' must be one of ' + Object.keys(internals.guidVersions).join(', '));
355
+ Hoek.assert(!(versions.has(versionNumber)), 'version at position ' + i + ' must not be a duplicate.');
356
+
357
+ versionNumbers += versionNumber;
358
+ versions.add(versionNumber);
355
359
  }
356
360
  }
357
361
 
358
- const regex = /^([\[{\(]?)([0-9A-F]{8})([:-]?)([0-9A-F]{4})([:-]?)([0-9A-F]{4})([:-]?)([0-9A-F]{4})([:-]?)([0-9A-F]{12})([\]}\)]?)$/i;
362
+ const guidRegex = new RegExp(`^([\\[{\\(]?)[0-9A-F]{8}([:-]?)[0-9A-F]{4}\\2?[${versionNumbers || '0-9A-F'}][0-9A-F]{3}\\2?[${versionNumbers ? '89AB' : '0-9A-F'}][0-9A-F]{3}\\2?[0-9A-F]{12}([\\]}\\)]?)$`, 'i');
359
363
 
360
364
  return this._test('guid', guidOptions, function (value, state, options) {
361
365
 
362
- const results = regex.exec(value);
366
+ const results = guidRegex.exec(value);
363
367
 
364
368
  if (!results) {
365
369
  return this.createError('string.guid', { value }, state, options);
366
370
  }
367
371
 
368
372
  // Matching braces
369
- if (brackets[results[1]] !== results[11]) {
373
+ if (internals.guidBrackets[results[1]] !== results[results.length - 1]) {
370
374
  return this.createError('string.guid', { value }, state, options);
371
375
  }
372
376
 
373
- // Matching separators
374
- if (results[3] !== results[5] || results[3] !== results[7] || results[3] !== results[9]) {
375
- return this.createError('string.guid', { value }, state, options);
376
- }
377
-
378
- // Specific UUID versions
379
- if (versions.length) {
380
- const validVersions = versions.some((uuidVersion) => {
381
-
382
- return results[6][0] === uuids[uuidVersion];
383
- });
384
-
385
- // Valid version and 89AB check
386
- if (!(validVersions && /[89AB]/i.test(results[8][0]))) {
387
- return this.createError('string.guid', { value }, state, options);
388
- }
389
- }
390
-
391
377
  return value;
392
378
  });
393
379
  }
@@ -406,9 +392,26 @@ internals.String = class extends Any {
406
392
  });
407
393
  }
408
394
 
409
- base64() {
395
+ base64(base64Options) {
396
+
397
+ base64Options = base64Options || {};
398
+
399
+ // Validation.
400
+ Hoek.assert(typeof base64Options === 'object', 'base64 options must be an object');
401
+ Hoek.assert(typeof base64Options.paddingRequired === 'undefined' || typeof base64Options.paddingRequired === 'boolean',
402
+ 'paddingRequired must be boolean');
403
+
404
+ // Determine if padding is required.
405
+ const paddingRequired = base64Options.paddingRequired === false ?
406
+ base64Options.paddingRequired
407
+ : base64Options.paddingRequired || true;
410
408
 
411
- const regex = /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/;
409
+ // Set validation based on preference.
410
+ const regex = paddingRequired ?
411
+ // Padding is required.
412
+ /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/
413
+ // Padding is optional.
414
+ : /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}(==)?|[A-Za-z0-9+\/]{3}=?)?$/;
412
415
 
413
416
  return this._test('base64', regex, function (value, state, options) {
414
417
 
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "joi",
3
3
  "description": "Object schema validation",
4
- "version": "10.4.2",
4
+ "version": "10.6.0",
5
5
  "homepage": "https://github.com/hapijs/joi",
6
6
  "repository": "git://github.com/hapijs/joi",
7
7
  "main": "lib/index.js",