joi 13.4.0 → 13.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/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/v13.4.0/API.md).
19
+ See the detailed [API Reference](https://github.com/hapijs/joi/blob/v13.6.0/API.md).
20
20
 
21
21
  # Example
22
22
 
@@ -28,7 +28,7 @@ const schema = Joi.object().keys({
28
28
  password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
29
29
  access_token: [Joi.string(), Joi.number()],
30
30
  birthyear: Joi.number().integer().min(1900).max(2013),
31
- email: Joi.string().email()
31
+ email: Joi.string().email({ minDomainAtoms: 2 })
32
32
  }).with('username', 'birthyear').without('password', 'access_token');
33
33
 
34
34
  // Return result.
@@ -56,6 +56,7 @@ The above schema defines the following constraints:
56
56
  * an integer between 1900 and 2013
57
57
  * `email`
58
58
  * a valid email address string
59
+ * must have two domain parts e.g. `example.com`
59
60
 
60
61
  # Usage
61
62
 
package/lib/index.js CHANGED
@@ -132,7 +132,7 @@ internals.root = function () {
132
132
  return any.validate(value, callback);
133
133
  }
134
134
 
135
- const options = count === 2 ? args[1] : {};
135
+ const options = count === 2 ? args[1] : undefined;
136
136
  const schema = root.compile(args[0]);
137
137
 
138
138
  return schema._validateWithOptions(value, options, callback);
package/lib/language.js CHANGED
@@ -147,6 +147,7 @@ exports.errors = {
147
147
  hex: 'must only contain hexadecimal characters',
148
148
  hexAlign: 'hex decoded representation must be byte aligned',
149
149
  base64: 'must be a valid base64 string',
150
+ dataUri: 'must be a valid dataUri string',
150
151
  hostname: 'must be a valid hostname',
151
152
  normalize: 'must be unicode normalized in the {{form}} form',
152
153
  lowercase: 'must only contain lowercase characters',
@@ -150,7 +150,7 @@ internals.Alternatives = class extends Any {
150
150
 
151
151
  describe() {
152
152
 
153
- const description = Any.prototype.describe.call(this);
153
+ const description = super.describe();
154
154
  const alternatives = [];
155
155
  for (let i = 0; i < this._inner.matches.length; ++i) {
156
156
  const item = this._inner.matches[i];
@@ -311,7 +311,7 @@ internals.Array = class extends Any {
311
311
 
312
312
  describe() {
313
313
 
314
- const description = Any.prototype.describe.call(this);
314
+ const description = super.describe();
315
315
 
316
316
  if (this._inner.ordereds.length) {
317
317
  description.orderedItems = [];
@@ -477,13 +477,19 @@ internals.Array = class extends Any {
477
477
  });
478
478
  }
479
479
 
480
- unique(comparator) {
480
+ unique(comparator, configs) {
481
481
 
482
482
  Hoek.assert(comparator === undefined ||
483
483
  typeof comparator === 'function' ||
484
484
  typeof comparator === 'string', 'comparator must be a function or a string');
485
485
 
486
- const settings = {};
486
+ Hoek.assert(configs === undefined ||
487
+ typeof configs === 'object', 'configs must be an object');
488
+
489
+ const settings = {
490
+ ignoreUndefined: (configs && configs.ignoreUndefined) || false
491
+ };
492
+
487
493
 
488
494
  if (typeof comparator === 'string') {
489
495
  settings.path = comparator;
@@ -505,6 +511,7 @@ internals.Array = class extends Any {
505
511
  };
506
512
 
507
513
  const compare = settings.comparator || Hoek.deepEqual;
514
+ const ignoreUndefined = settings.ignoreUndefined;
508
515
 
509
516
  for (let i = 0; i < value.length; ++i) {
510
517
  const item = settings.path ? Hoek.reach(value[i], settings.path) : value[i];
@@ -543,7 +550,7 @@ internals.Array = class extends Any {
543
550
  records.set(item, i);
544
551
  }
545
552
  else {
546
- if (records[item] !== undefined) {
553
+ if ((!ignoreUndefined || item !== undefined) && records[item] !== undefined) {
547
554
  const localState = {
548
555
  key: state.key,
549
556
  path: state.path.concat(i),
@@ -87,7 +87,7 @@ internals.Boolean = class extends Any {
87
87
 
88
88
  describe() {
89
89
 
90
- const description = Any.prototype.describe.call(this);
90
+ const description = super.describe();
91
91
  description.truthy = [true].concat(this._inner.truthySet.values());
92
92
  description.falsy = [false].concat(this._inner.falsySet.values());
93
93
  return description;
@@ -47,7 +47,7 @@ internals.Number = class extends Any {
47
47
  result.value = Math.round(result.value * precision) / precision;
48
48
  }
49
49
 
50
- result.errors = isNumber ? null : this.createError('number.base', null, state, options);
50
+ result.errors = isNumber ? null : this.createError('number.base', { value }, state, options);
51
51
  return result;
52
52
  }
53
53
 
@@ -615,7 +615,7 @@ internals.Object = class extends Any {
615
615
 
616
616
  describe(shallow) {
617
617
 
618
- const description = Any.prototype.describe.call(this);
618
+ const description = super.describe();
619
619
 
620
620
  if (description.rules) {
621
621
  for (let i = 0; i < description.rules.length; ++i) {
@@ -276,11 +276,15 @@ internals.String = class extends Any {
276
276
  let customScheme = '';
277
277
  let allowRelative = false;
278
278
  let relativeOnly = false;
279
+ let allowQuerySquareBrackets = false;
279
280
  let regex = internals.uriRegex;
280
281
 
281
282
  if (uriOptions) {
282
283
  Hoek.assert(typeof uriOptions === 'object', 'options must be an object');
283
284
 
285
+ const unknownOptions = Object.keys(uriOptions).filter((key) => !['scheme', 'allowRelative', 'relativeOnly', 'allowQuerySquareBrackets'].includes(key));
286
+ Hoek.assert(unknownOptions.length === 0, `options contain unknown keys: ${unknownOptions}`);
287
+
284
288
  if (uriOptions.scheme) {
285
289
  Hoek.assert(uriOptions.scheme instanceof RegExp || typeof uriOptions.scheme === 'string' || Array.isArray(uriOptions.scheme), 'scheme must be a RegExp, String, or Array');
286
290
 
@@ -316,10 +320,14 @@ internals.String = class extends Any {
316
320
  if (uriOptions.relativeOnly) {
317
321
  relativeOnly = true;
318
322
  }
323
+
324
+ if (uriOptions.allowQuerySquareBrackets) {
325
+ allowQuerySquareBrackets = true;
326
+ }
319
327
  }
320
328
 
321
- if (customScheme || allowRelative || relativeOnly) {
322
- regex = Uri.createUriRegex(customScheme, allowRelative, relativeOnly);
329
+ if (customScheme || allowRelative || relativeOnly || allowQuerySquareBrackets) {
330
+ regex = Uri.createUriRegex(customScheme, allowRelative, relativeOnly, allowQuerySquareBrackets);
323
331
  }
324
332
 
325
333
  return this._test('uri', uriOptions, function (value, state, options) {
@@ -460,6 +468,41 @@ internals.String = class extends Any {
460
468
  });
461
469
  }
462
470
 
471
+ dataUri(dataUriOptions = {}) {
472
+
473
+ const regex = /^data:[\w\/\+]+;((charset=[\w-]+|base64),)?(.*)$/;
474
+
475
+ // Determine if padding is required.
476
+ const paddingRequired = dataUriOptions.paddingRequired === false ?
477
+ dataUriOptions.paddingRequired
478
+ : dataUriOptions.paddingRequired || true;
479
+
480
+ const base64regex = paddingRequired ?
481
+ /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/
482
+ : /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}(==)?|[A-Za-z0-9+\/]{3}=?)?$/;
483
+
484
+ return this._test('dataUri', regex, function (value, state, options) {
485
+
486
+ const matches = value.match(regex);
487
+
488
+ if (matches) {
489
+ if (!matches[2]) {
490
+ return value;
491
+ }
492
+
493
+ if (matches[2] !== 'base64') {
494
+ return value;
495
+ }
496
+
497
+ if (base64regex.test(matches[3])) {
498
+ return value;
499
+ }
500
+ }
501
+
502
+ return this.createError('string.dataUri', { value }, state, options);
503
+ });
504
+ }
505
+
463
506
  hostname() {
464
507
 
465
508
  const regex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
@@ -529,20 +572,33 @@ internals.String = class extends Any {
529
572
  return obj;
530
573
  }
531
574
 
532
- trim() {
575
+ trim(enabled = true) {
533
576
 
534
- const obj = this._test('trim', undefined, function (value, state, options) {
577
+ Hoek.assert(typeof enabled === 'boolean', 'option must be a boolean');
535
578
 
536
- if (options.convert ||
537
- value === value.trim()) {
579
+ if ((this._flags.trim && enabled) || (!this._flags.trim && !enabled)) {
580
+ return this;
581
+ }
538
582
 
539
- return value;
540
- }
583
+ let obj;
584
+ if (enabled) {
585
+ obj = this._test('trim', undefined, function (value, state, options) {
541
586
 
542
- return this.createError('string.trim', { value }, state, options);
543
- });
587
+ if (options.convert ||
588
+ value === value.trim()) {
589
+
590
+ return value;
591
+ }
592
+
593
+ return this.createError('string.trim', { value }, state, options);
594
+ });
595
+ }
596
+ else {
597
+ obj = this.clone();
598
+ obj._tests = obj._tests.filter((test) => test.name !== 'trim');
599
+ }
544
600
 
545
- obj._flags.trim = true;
601
+ obj._flags.trim = enabled;
546
602
  return obj;
547
603
  }
548
604
 
@@ -78,6 +78,11 @@ internals.generate = function () {
78
78
  const pchar = unreserved + pctEncoded + subDelims + ':@';
79
79
  const pcharOnly = '[' + pchar + ']';
80
80
 
81
+ /**
82
+ * squareBrackets example: []
83
+ */
84
+ const squareBrackets = '\\[\\]';
85
+
81
86
  /**
82
87
  * dec-octet = DIGIT ; 0-9
83
88
  * / %x31-39 DIGIT ; 10-99
@@ -197,6 +202,11 @@ internals.generate = function () {
197
202
  */
198
203
  internals.rfc3986.query = '[' + pchar + '\\/\\?]*(?=#|$)'; //Finish matching either at the fragment part or end of the line.
199
204
 
205
+ /**
206
+ * query = *( pchar / "[" / "]" / "/" / "?" )
207
+ */
208
+ internals.rfc3986.queryWithSquareBrackets = '[' + pchar + squareBrackets + '\\/\\?]*(?=#|$)'; //Finish matching either at the fragment part or end of the line.
209
+
200
210
  /**
201
211
  * fragment = *( pchar / "/" / "?" )
202
212
  */
@@ -9,7 +9,7 @@ const RFC3986 = require('./rfc3986');
9
9
 
10
10
  const internals = {
11
11
  Uri: {
12
- createUriRegex: function (optionalScheme, allowRelative, relativeOnly) {
12
+ createUriRegex: function (optionalScheme, allowRelative, relativeOnly, allowQuerySquareBrackets) {
13
13
 
14
14
  let scheme = RFC3986.scheme;
15
15
  let prefix;
@@ -37,7 +37,7 @@ const internals = {
37
37
  *
38
38
  * relative-ref = relative-part [ "?" query ] [ "#" fragment ]
39
39
  */
40
- return new RegExp('^' + prefix + '(?:\\?' + RFC3986.query + ')?' + '(?:#' + RFC3986.fragment + ')?$');
40
+ return new RegExp('^' + prefix + '(?:\\?' + (allowQuerySquareBrackets ? RFC3986.queryWithSquareBrackets : RFC3986.query) + ')?' + '(?:#' + RFC3986.fragment + ')?$');
41
41
  }
42
42
  }
43
43
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "joi",
3
3
  "description": "Object schema validation",
4
- "version": "13.4.0",
4
+ "version": "13.6.0",
5
5
  "homepage": "https://github.com/hapijs/joi",
6
6
  "repository": "git://github.com/hapijs/joi",
7
7
  "main": "lib/index.js",