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 +3 -2
- package/lib/index.js +1 -1
- package/lib/language.js +1 -0
- package/lib/types/alternatives/index.js +1 -1
- package/lib/types/array/index.js +11 -4
- package/lib/types/boolean/index.js +1 -1
- package/lib/types/number/index.js +1 -1
- package/lib/types/object/index.js +1 -1
- package/lib/types/string/index.js +67 -11
- package/lib/types/string/rfc3986.js +10 -0
- package/lib/types/string/uri.js +2 -2
- 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/v13.
|
|
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 =
|
|
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];
|
package/lib/types/array/index.js
CHANGED
|
@@ -311,7 +311,7 @@ internals.Array = class extends Any {
|
|
|
311
311
|
|
|
312
312
|
describe() {
|
|
313
313
|
|
|
314
|
-
const description =
|
|
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
|
-
|
|
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 =
|
|
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',
|
|
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 =
|
|
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
|
-
|
|
577
|
+
Hoek.assert(typeof enabled === 'boolean', 'option must be a boolean');
|
|
535
578
|
|
|
536
|
-
|
|
537
|
-
|
|
579
|
+
if ((this._flags.trim && enabled) || (!this._flags.trim && !enabled)) {
|
|
580
|
+
return this;
|
|
581
|
+
}
|
|
538
582
|
|
|
539
|
-
|
|
540
|
-
|
|
583
|
+
let obj;
|
|
584
|
+
if (enabled) {
|
|
585
|
+
obj = this._test('trim', undefined, function (value, state, options) {
|
|
541
586
|
|
|
542
|
-
|
|
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 =
|
|
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
|
*/
|
package/lib/types/string/uri.js
CHANGED
|
@@ -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
|
};
|