joi 17.1.1 → 17.4.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.md +2 -2
- package/README.md +8 -10
- package/dist/joi-browser.min.js +1 -1
- package/lib/base.js +35 -0
- package/lib/common.js +5 -2
- package/lib/extend.js +3 -2
- package/lib/index.d.ts +2207 -0
- package/lib/manifest.js +1 -1
- package/lib/ref.js +8 -6
- package/lib/schemas.js +8 -1
- package/lib/template.js +27 -10
- package/lib/trace.js +1 -1
- package/lib/types/alternatives.js +12 -8
- package/lib/types/any.js +1 -1
- package/lib/types/array.js +35 -3
- package/lib/types/date.js +4 -4
- package/lib/types/keys.js +13 -8
- package/lib/types/number.js +7 -3
- package/lib/types/string.js +45 -20
- package/lib/validator.js +37 -4
- package/package.json +35 -33
package/lib/manifest.js
CHANGED
package/lib/ref.js
CHANGED
|
@@ -25,7 +25,7 @@ const internals = {
|
|
|
25
25
|
exports.create = function (key, options = {}) {
|
|
26
26
|
|
|
27
27
|
Assert(typeof key === 'string', 'Invalid reference key:', key);
|
|
28
|
-
Common.assertOptions(options, ['adjust', 'ancestor', 'in', 'iterables', 'map', 'prefix', 'separator']);
|
|
28
|
+
Common.assertOptions(options, ['adjust', 'ancestor', 'in', 'iterables', 'map', 'prefix', 'render', 'separator']);
|
|
29
29
|
Assert(!options.prefix || typeof options.prefix === 'object', 'options.prefix must be of type object');
|
|
30
30
|
|
|
31
31
|
const ref = Object.assign({}, internals.defaults, options);
|
|
@@ -77,7 +77,7 @@ exports.create = function (key, options = {}) {
|
|
|
77
77
|
|
|
78
78
|
exports.in = function (key, options = {}) {
|
|
79
79
|
|
|
80
|
-
return exports.create(key,
|
|
80
|
+
return exports.create(key, { ...options, in: true });
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
|
|
@@ -93,8 +93,8 @@ internals.Ref = class {
|
|
|
93
93
|
|
|
94
94
|
Assert(typeof options === 'object', 'Invalid reference construction');
|
|
95
95
|
Common.assertOptions(options, [
|
|
96
|
-
'adjust', 'ancestor', 'in', 'iterables', 'map', 'path', 'separator', 'type',
|
|
97
|
-
'depth', 'key', 'root', 'display'
|
|
96
|
+
'adjust', 'ancestor', 'in', 'iterables', 'map', 'path', 'render', 'separator', 'type', // Copied
|
|
97
|
+
'depth', 'key', 'root', 'display' // Overridden
|
|
98
98
|
]);
|
|
99
99
|
|
|
100
100
|
Assert([false, undefined].includes(options.separator) || typeof options.separator === 'string' && options.separator.length === 1, 'Invalid separator');
|
|
@@ -211,8 +211,10 @@ internals.Ref = class {
|
|
|
211
211
|
ref.map = [...this.map];
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
for (const key of ['adjust', 'iterables']) {
|
|
215
|
-
if (this[key] !== null
|
|
214
|
+
for (const key of ['adjust', 'iterables', 'render']) {
|
|
215
|
+
if (this[key] !== null &&
|
|
216
|
+
this[key] !== undefined) {
|
|
217
|
+
|
|
216
218
|
ref[key] = this[key];
|
|
217
219
|
}
|
|
218
220
|
}
|
package/lib/schemas.js
CHANGED
|
@@ -17,6 +17,7 @@ internals.wrap = Joi.string()
|
|
|
17
17
|
exports.preferences = Joi.object({
|
|
18
18
|
allowUnknown: Joi.boolean(),
|
|
19
19
|
abortEarly: Joi.boolean(),
|
|
20
|
+
artifacts: Joi.boolean(),
|
|
20
21
|
cache: Joi.boolean(),
|
|
21
22
|
context: Joi.object(),
|
|
22
23
|
convert: Joi.boolean(),
|
|
@@ -89,6 +90,10 @@ exports.extension = Joi.object({
|
|
|
89
90
|
])
|
|
90
91
|
.required(),
|
|
91
92
|
args: Joi.function(),
|
|
93
|
+
cast: Joi.object().pattern(internals.nameRx, Joi.object({
|
|
94
|
+
from: Joi.function().maxArity(1).required(),
|
|
95
|
+
to: Joi.function().minArity(1).maxArity(2).required()
|
|
96
|
+
})),
|
|
92
97
|
base: Joi.object().schema()
|
|
93
98
|
.when('type', { is: Joi.object().regex(), then: Joi.forbidden() }),
|
|
94
99
|
coerce: [
|
|
@@ -157,7 +162,8 @@ internals.desc = {
|
|
|
157
162
|
map: Joi.array().items(Joi.array().length(2)).min(1),
|
|
158
163
|
adjust: Joi.function(),
|
|
159
164
|
iterables: Joi.boolean(),
|
|
160
|
-
in: Joi.boolean()
|
|
165
|
+
in: Joi.boolean(),
|
|
166
|
+
render: Joi.boolean()
|
|
161
167
|
})
|
|
162
168
|
.required()
|
|
163
169
|
}),
|
|
@@ -244,6 +250,7 @@ exports.description = Joi.object({
|
|
|
244
250
|
preferences: {
|
|
245
251
|
allowUnknown: Joi.boolean(),
|
|
246
252
|
abortEarly: Joi.boolean(),
|
|
253
|
+
artifacts: Joi.boolean(),
|
|
247
254
|
cache: Joi.boolean(),
|
|
248
255
|
convert: Joi.boolean(),
|
|
249
256
|
dateFormat: ['date', 'iso', 'string', 'time', 'utc'],
|
package/lib/template.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const Assert = require('@hapi/hoek/lib/assert');
|
|
4
4
|
const Clone = require('@hapi/hoek/lib/clone');
|
|
5
5
|
const EscapeHtml = require('@hapi/hoek/lib/escapeHtml');
|
|
6
|
-
const Formula = require('@
|
|
6
|
+
const Formula = require('@sideway/formula');
|
|
7
7
|
|
|
8
8
|
const Common = require('./common');
|
|
9
9
|
const Errors = require('./errors');
|
|
@@ -78,8 +78,13 @@ module.exports = exports = internals.Template = class {
|
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
const
|
|
81
|
+
let variable = part.slice(raw ? 0 : 1, end);
|
|
82
|
+
const wrapped = variable[0] === ':';
|
|
83
|
+
if (wrapped) {
|
|
84
|
+
variable = variable.slice(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const dynamic = this._ref(internals.decode(variable), { raw, wrapped });
|
|
83
88
|
processed.push(dynamic);
|
|
84
89
|
if (typeof dynamic !== 'string') {
|
|
85
90
|
refs = true;
|
|
@@ -184,11 +189,10 @@ module.exports = exports = internals.Template = class {
|
|
|
184
189
|
}
|
|
185
190
|
else {
|
|
186
191
|
const rendered = this._part(part, /* context -> [*/ value, state, prefs, local, options /*] */);
|
|
187
|
-
const string = internals.stringify(rendered, prefs, options
|
|
192
|
+
const string = internals.stringify(rendered, value, state, prefs, local, options);
|
|
188
193
|
if (string !== undefined) {
|
|
189
194
|
const result = part.raw || (options.errors && options.errors.escapeHtml) === false ? string : EscapeHtml(string);
|
|
190
|
-
|
|
191
|
-
parts.push(internals.wrap(result, ends));
|
|
195
|
+
parts.push(internals.wrap(result, part.wrapped && prefs.errors.wrap.label));
|
|
192
196
|
}
|
|
193
197
|
}
|
|
194
198
|
}
|
|
@@ -196,7 +200,7 @@ module.exports = exports = internals.Template = class {
|
|
|
196
200
|
return parts.join('');
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
_ref(content, raw) {
|
|
203
|
+
_ref(content, { raw, wrapped }) {
|
|
200
204
|
|
|
201
205
|
const refs = [];
|
|
202
206
|
const reference = (variable) => {
|
|
@@ -216,7 +220,8 @@ module.exports = exports = internals.Template = class {
|
|
|
216
220
|
|
|
217
221
|
if (formula.single) {
|
|
218
222
|
if (formula.single.type === 'reference') {
|
|
219
|
-
|
|
223
|
+
const ref = refs[0];
|
|
224
|
+
return { ref, raw, refs, wrapped: wrapped || ref.type === 'local' && ref.key === 'label' };
|
|
220
225
|
}
|
|
221
226
|
|
|
222
227
|
return internals.stringify(formula.single.value);
|
|
@@ -302,10 +307,18 @@ internals.wrap = function (value, ends) {
|
|
|
302
307
|
};
|
|
303
308
|
|
|
304
309
|
|
|
305
|
-
internals.stringify = function (value, prefs, options) {
|
|
310
|
+
internals.stringify = function (value, original, state, prefs, local, options) {
|
|
306
311
|
|
|
307
312
|
const type = typeof value;
|
|
308
313
|
|
|
314
|
+
let skipWrap = false;
|
|
315
|
+
if (Ref.isRef(value) &&
|
|
316
|
+
value.render) {
|
|
317
|
+
|
|
318
|
+
skipWrap = value.in;
|
|
319
|
+
value = value.resolve(original, state, prefs, local, { in: value.in, ...options });
|
|
320
|
+
}
|
|
321
|
+
|
|
309
322
|
if (value === null) {
|
|
310
323
|
return 'null';
|
|
311
324
|
}
|
|
@@ -344,7 +357,11 @@ internals.stringify = function (value, prefs, options) {
|
|
|
344
357
|
|
|
345
358
|
let partial = '';
|
|
346
359
|
for (const item of value) {
|
|
347
|
-
partial = partial + (partial.length ? ', ' : '') + internals.stringify(item, prefs, options);
|
|
360
|
+
partial = partial + (partial.length ? ', ' : '') + internals.stringify(item, original, state, prefs, local, options);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (skipWrap) {
|
|
364
|
+
return partial;
|
|
348
365
|
}
|
|
349
366
|
|
|
350
367
|
return internals.wrap(partial, prefs.errors.wrap.array);
|
package/lib/trace.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Assert = require('@hapi/hoek/lib/assert');
|
|
4
|
+
const Merge = require('@hapi/hoek/lib/merge');
|
|
4
5
|
|
|
5
6
|
const Any = require('./any');
|
|
6
7
|
const Common = require('../common');
|
|
@@ -44,8 +45,7 @@ module.exports = Any.extend({
|
|
|
44
45
|
// Match all or one
|
|
45
46
|
|
|
46
47
|
if (schema._flags.match) {
|
|
47
|
-
|
|
48
|
-
let matched;
|
|
48
|
+
const matched = [];
|
|
49
49
|
|
|
50
50
|
for (let i = 0; i < schema.$_terms.matches.length; ++i) {
|
|
51
51
|
const item = schema.$_terms.matches[i];
|
|
@@ -54,23 +54,27 @@ module.exports = Any.extend({
|
|
|
54
54
|
|
|
55
55
|
const result = item.schema.$_validate(value, localState, prefs);
|
|
56
56
|
if (!result.errors) {
|
|
57
|
-
|
|
58
|
-
matched = result.value;
|
|
57
|
+
matched.push(result.value);
|
|
59
58
|
}
|
|
60
59
|
else {
|
|
61
60
|
localState.restore();
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
if (
|
|
64
|
+
if (matched.length === 0) {
|
|
66
65
|
return { errors: error('alternatives.any') };
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
if (schema._flags.match === 'one') {
|
|
70
|
-
return
|
|
69
|
+
return matched.length === 1 ? { value: matched[0] } : { errors: error('alternatives.one') };
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
if (matched.length !== schema.$_terms.matches.length) {
|
|
73
|
+
return { errors: error('alternatives.all') };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const allobj = schema.$_terms.matches.reduce((acc, v) => acc && v.schema.type === 'object', true);
|
|
77
|
+
return allobj ? { value: matched.reduce((acc, v) => Merge(acc, v, { mergeArrays: false })) } : { value: matched[matched.length - 1] };
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
// Match any
|
|
@@ -183,7 +187,7 @@ module.exports = Any.extend({
|
|
|
183
187
|
|
|
184
188
|
label(name) {
|
|
185
189
|
|
|
186
|
-
const obj = this.$
|
|
190
|
+
const obj = this.$_parent('label', name);
|
|
187
191
|
const each = (item, source) => (source.path[0] !== 'is' ? item.label(name) : undefined);
|
|
188
192
|
return obj.$_modify({ each, ref: false });
|
|
189
193
|
}
|
package/lib/types/any.js
CHANGED
|
@@ -167,7 +167,7 @@ module.exports = Base.extend({
|
|
|
167
167
|
'any.failover': '{{#label}} threw an error when running failover method',
|
|
168
168
|
'any.invalid': '{{#label}} contains an invalid value',
|
|
169
169
|
'any.only': '{{#label}} must be {if(#valids.length == 1, "", "one of ")}{{#valids}}',
|
|
170
|
-
'any.ref': '{{#label}} {{#arg}} references
|
|
170
|
+
'any.ref': '{{#label}} {{#arg}} references {{:#ref}} which {{#reason}}',
|
|
171
171
|
'any.required': '{{#label}} is required',
|
|
172
172
|
'any.unknown': '{{#label}} is not allowed'
|
|
173
173
|
}
|
package/lib/types/array.js
CHANGED
|
@@ -114,7 +114,7 @@ module.exports = Any.extend({
|
|
|
114
114
|
|
|
115
115
|
return obj.$_mutateRebuild();
|
|
116
116
|
},
|
|
117
|
-
validate(value, { schema, error, state, prefs }) {
|
|
117
|
+
validate(value, { schema, error, state, prefs, errorsArray }) {
|
|
118
118
|
|
|
119
119
|
const requireds = schema.$_terms._requireds.slice();
|
|
120
120
|
const ordereds = schema.$_terms.ordered.slice();
|
|
@@ -123,7 +123,8 @@ module.exports = Any.extend({
|
|
|
123
123
|
const wasArray = !value[Common.symbols.arraySingle];
|
|
124
124
|
delete value[Common.symbols.arraySingle];
|
|
125
125
|
|
|
126
|
-
const errors =
|
|
126
|
+
const errors = errorsArray();
|
|
127
|
+
|
|
127
128
|
let il = value.length;
|
|
128
129
|
for (let i = 0; i < il; ++i) {
|
|
129
130
|
const item = value[i];
|
|
@@ -341,6 +342,10 @@ module.exports = Any.extend({
|
|
|
341
342
|
|
|
342
343
|
if (ordereds.length) {
|
|
343
344
|
internals.fillOrderedErrors(schema, errors, ordereds, value, state, prefs);
|
|
345
|
+
|
|
346
|
+
if (!errors.length) {
|
|
347
|
+
internals.fillDefault(ordereds, value, state, prefs);
|
|
348
|
+
}
|
|
344
349
|
}
|
|
345
350
|
|
|
346
351
|
return errors.length ? errors : value;
|
|
@@ -611,7 +616,7 @@ module.exports = Any.extend({
|
|
|
611
616
|
messages: {
|
|
612
617
|
'array.base': '{{#label}} must be an array',
|
|
613
618
|
'array.excludes': '{{#label}} contains an excluded value',
|
|
614
|
-
'array.hasKnown': '{{#label}} does not contain at least one required match for type
|
|
619
|
+
'array.hasKnown': '{{#label}} does not contain at least one required match for type {:#patternLabel}',
|
|
615
620
|
'array.hasUnknown': '{{#label}} does not contain at least one required match',
|
|
616
621
|
'array.includes': '{{#label}} does not match any of the allowed types',
|
|
617
622
|
'array.includesRequiredBoth': '{{#label}} does not contain {{#knownMisses}} and {{#unknownMisses}} other required value(s)',
|
|
@@ -676,6 +681,33 @@ internals.fillOrderedErrors = function (schema, errors, ordereds, value, state,
|
|
|
676
681
|
};
|
|
677
682
|
|
|
678
683
|
|
|
684
|
+
internals.fillDefault = function (ordereds, value, state, prefs) {
|
|
685
|
+
|
|
686
|
+
const overrides = [];
|
|
687
|
+
let trailingUndefined = true;
|
|
688
|
+
|
|
689
|
+
for (let i = ordereds.length - 1; i >= 0; --i) {
|
|
690
|
+
const ordered = ordereds[i];
|
|
691
|
+
const ancestors = [value, ...state.ancestors];
|
|
692
|
+
const override = ordered.$_validate(undefined, state.localize(state.path, ancestors, ordered), prefs).value;
|
|
693
|
+
|
|
694
|
+
if (trailingUndefined) {
|
|
695
|
+
if (override === undefined) {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
trailingUndefined = false;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
overrides.unshift(override);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (overrides.length) {
|
|
706
|
+
value.push(...overrides);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
|
|
679
711
|
internals.fastSplice = function (arr, i) {
|
|
680
712
|
|
|
681
713
|
let pos = i;
|
package/lib/types/date.js
CHANGED
|
@@ -149,10 +149,10 @@ module.exports = Any.extend({
|
|
|
149
149
|
messages: {
|
|
150
150
|
'date.base': '{{#label}} must be a valid date',
|
|
151
151
|
'date.format': '{{#label}} must be in {msg("date.format." + #format) || #format} format',
|
|
152
|
-
'date.greater': '{{#label}} must be greater than
|
|
153
|
-
'date.less': '{{#label}} must be less than
|
|
154
|
-
'date.max': '{{#label}} must be less than or equal to
|
|
155
|
-
'date.min': '{{#label}} must be
|
|
152
|
+
'date.greater': '{{#label}} must be greater than {{:#limit}}',
|
|
153
|
+
'date.less': '{{#label}} must be less than {{:#limit}}',
|
|
154
|
+
'date.max': '{{#label}} must be less than or equal to {{:#limit}}',
|
|
155
|
+
'date.min': '{{#label}} must be greater than or equal to {{:#limit}}',
|
|
156
156
|
|
|
157
157
|
// Messages used in date.format
|
|
158
158
|
|
package/lib/types/keys.js
CHANGED
|
@@ -27,6 +27,7 @@ module.exports = Any.extend({
|
|
|
27
27
|
type: '_keys',
|
|
28
28
|
|
|
29
29
|
properties: {
|
|
30
|
+
|
|
30
31
|
typeof: 'object'
|
|
31
32
|
},
|
|
32
33
|
|
|
@@ -111,6 +112,10 @@ module.exports = Any.extend({
|
|
|
111
112
|
return { value, errors: result.errors };
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
if (result.value !== undefined) {
|
|
116
|
+
value[key] = result.value;
|
|
117
|
+
}
|
|
118
|
+
|
|
114
119
|
errors.push(...result.errors);
|
|
115
120
|
}
|
|
116
121
|
else if (child.schema._flags.result === 'strip' ||
|
|
@@ -478,7 +483,7 @@ module.exports = Any.extend({
|
|
|
478
483
|
value = Common.symbols.deepDefault;
|
|
479
484
|
}
|
|
480
485
|
|
|
481
|
-
return this.$
|
|
486
|
+
return this.$_parent('default', value, options);
|
|
482
487
|
}
|
|
483
488
|
},
|
|
484
489
|
|
|
@@ -528,22 +533,22 @@ module.exports = Any.extend({
|
|
|
528
533
|
'object.and': '{{#label}} contains {{#presentWithLabels}} without its required peers {{#missingWithLabels}}',
|
|
529
534
|
'object.assert': '{{#label}} is invalid because {if(#subject.key, `"` + #subject.key + `" failed to ` + (#message || "pass the assertion test"), #message || "the assertion failed")}',
|
|
530
535
|
'object.base': '{{#label}} must be of type {{#type}}',
|
|
531
|
-
'object.instance': '{{#label}} must be an instance of
|
|
536
|
+
'object.instance': '{{#label}} must be an instance of {{:#type}}',
|
|
532
537
|
'object.length': '{{#label}} must have {{#limit}} key{if(#limit == 1, "", "s")}',
|
|
533
538
|
'object.max': '{{#label}} must have less than or equal to {{#limit}} key{if(#limit == 1, "", "s")}',
|
|
534
539
|
'object.min': '{{#label}} must have at least {{#limit}} key{if(#limit == 1, "", "s")}',
|
|
535
540
|
'object.missing': '{{#label}} must contain at least one of {{#peersWithLabels}}',
|
|
536
|
-
'object.nand': '
|
|
541
|
+
'object.nand': '{{:#mainWithLabel}} must not exist simultaneously with {{#peersWithLabels}}',
|
|
537
542
|
'object.oxor': '{{#label}} contains a conflict between optional exclusive peers {{#peersWithLabels}}',
|
|
538
543
|
'object.pattern.match': '{{#label}} keys failed to match pattern requirements',
|
|
539
544
|
'object.refType': '{{#label}} must be a Joi reference',
|
|
540
545
|
'object.regex': '{{#label}} must be a RegExp object',
|
|
541
|
-
'object.rename.multiple': '{{#label}} cannot rename
|
|
542
|
-
'object.rename.override': '{{#label}} cannot rename
|
|
546
|
+
'object.rename.multiple': '{{#label}} cannot rename {{:#from}} because multiple renames are disabled and another key was already renamed to {{:#to}}',
|
|
547
|
+
'object.rename.override': '{{#label}} cannot rename {{:#from}} because override is disabled and target {{:#to}} exists',
|
|
543
548
|
'object.schema': '{{#label}} must be a Joi schema of {{#type}} type',
|
|
544
549
|
'object.unknown': '{{#label}} is not allowed',
|
|
545
|
-
'object.with': '
|
|
546
|
-
'object.without': '
|
|
550
|
+
'object.with': '{{:#mainWithLabel}} missing required peer {{:#peerWithLabel}}',
|
|
551
|
+
'object.without': '{{:#mainWithLabel}} conflict with forbidden peer {{:#peerWithLabel}}',
|
|
547
552
|
'object.xor': '{{#label}} contains a conflict between exclusive peers {{#peersWithLabels}}'
|
|
548
553
|
}
|
|
549
554
|
});
|
|
@@ -599,7 +604,7 @@ internals.dependency = function (schema, rel, key, peers, options) {
|
|
|
599
604
|
const separator = Common.default(options.separator, '.');
|
|
600
605
|
const paths = [];
|
|
601
606
|
for (const peer of peers) {
|
|
602
|
-
Assert(typeof peer === 'string', rel, 'peers must be
|
|
607
|
+
Assert(typeof peer === 'string', rel, 'peers must be strings');
|
|
603
608
|
paths.push(Compile.ref(peer, { separator, ancestor: 0, prefix: false }));
|
|
604
609
|
}
|
|
605
610
|
|
package/lib/types/number.js
CHANGED
|
@@ -284,7 +284,7 @@ module.exports = Any.extend({
|
|
|
284
284
|
'number.integer': '{{#label}} must be an integer',
|
|
285
285
|
'number.less': '{{#label}} must be less than {{#limit}}',
|
|
286
286
|
'number.max': '{{#label}} must be less than or equal to {{#limit}}',
|
|
287
|
-
'number.min': '{{#label}} must be
|
|
287
|
+
'number.min': '{{#label}} must be greater than or equal to {{#limit}}',
|
|
288
288
|
'number.multiple': '{{#label}} must be a multiple of {{#multiple}}',
|
|
289
289
|
'number.negative': '{{#label}} must be a negative number',
|
|
290
290
|
'number.port': '{{#label}} must be a valid port',
|
|
@@ -312,10 +312,14 @@ internals.normalizeExponent = function (str) {
|
|
|
312
312
|
internals.normalizeDecimal = function (str) {
|
|
313
313
|
|
|
314
314
|
str = str
|
|
315
|
+
// Remove leading plus signs
|
|
315
316
|
.replace(/^\+/, '')
|
|
316
|
-
|
|
317
|
+
// Remove trailing zeros if there is a decimal point and unecessary decimal points
|
|
318
|
+
.replace(/\.0*$/, '')
|
|
319
|
+
// Add a integer 0 if the numbers starts with a decimal point
|
|
317
320
|
.replace(/^(-?)\.([^\.]*)$/, '$10.$2')
|
|
318
|
-
|
|
321
|
+
// Remove leading zeros
|
|
322
|
+
.replace(/^(-?)0+([0-9])/, '$1$2');
|
|
319
323
|
|
|
320
324
|
if (str.includes('.') &&
|
|
321
325
|
str.endsWith('0')) {
|
package/lib/types/string.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Assert = require('@hapi/hoek/lib/assert');
|
|
4
|
-
const Domain = require('@
|
|
5
|
-
const Email = require('@
|
|
6
|
-
const Ip = require('@
|
|
4
|
+
const Domain = require('@sideway/address/lib/domain');
|
|
5
|
+
const Email = require('@sideway/address/lib/email');
|
|
6
|
+
const Ip = require('@sideway/address/lib/ip');
|
|
7
7
|
const EscapeRegex = require('@hapi/hoek/lib/escapeRegex');
|
|
8
|
-
const Tlds = require('@
|
|
9
|
-
const Uri = require('@
|
|
8
|
+
const Tlds = require('@sideway/address/lib/tlds');
|
|
9
|
+
const Uri = require('@sideway/address/lib/uri');
|
|
10
10
|
|
|
11
11
|
const Any = require('./any');
|
|
12
12
|
const Common = require('../common');
|
|
@@ -41,8 +41,8 @@ const internals = {
|
|
|
41
41
|
uuidv4: '4',
|
|
42
42
|
uuidv5: '5'
|
|
43
43
|
},
|
|
44
|
+
guidSeparators: new Set([undefined, true, false, '-', ':']),
|
|
44
45
|
|
|
45
|
-
cidrPresences: ['required', 'optional', 'forbidden'],
|
|
46
46
|
normalizationForms: ['NFC', 'NFD', 'NFKC', 'NFKD']
|
|
47
47
|
};
|
|
48
48
|
|
|
@@ -257,7 +257,7 @@ module.exports = Any.extend({
|
|
|
257
257
|
method(options) {
|
|
258
258
|
|
|
259
259
|
if (options) {
|
|
260
|
-
Common.assertOptions(options, ['allowUnicode', 'minDomainSegments', 'tlds']);
|
|
260
|
+
Common.assertOptions(options, ['allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']);
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
const address = internals.addressOptions(options);
|
|
@@ -276,7 +276,7 @@ module.exports = Any.extend({
|
|
|
276
276
|
email: {
|
|
277
277
|
method(options = {}) {
|
|
278
278
|
|
|
279
|
-
Common.assertOptions(options, ['allowUnicode', 'ignoreLength', 'minDomainSegments', 'multiple', 'separator', 'tlds']);
|
|
279
|
+
Common.assertOptions(options, ['allowUnicode', 'ignoreLength', 'maxDomainSegments', 'minDomainSegments', 'multiple', 'separator', 'tlds']);
|
|
280
280
|
Assert(options.multiple === undefined || typeof options.multiple === 'boolean', 'multiple option must be an boolean');
|
|
281
281
|
|
|
282
282
|
const address = internals.addressOptions(options);
|
|
@@ -306,7 +306,7 @@ module.exports = Any.extend({
|
|
|
306
306
|
alias: 'uuid',
|
|
307
307
|
method(options = {}) {
|
|
308
308
|
|
|
309
|
-
Common.assertOptions(options, ['version']);
|
|
309
|
+
Common.assertOptions(options, ['version', 'separator']);
|
|
310
310
|
|
|
311
311
|
let versionNumbers = '';
|
|
312
312
|
|
|
@@ -328,7 +328,12 @@ module.exports = Any.extend({
|
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
|
|
331
|
+
Assert(internals.guidSeparators.has(options.separator), 'separator must be one of true, false, "-", or ":"');
|
|
332
|
+
const separator = options.separator === undefined ? '[:-]?' :
|
|
333
|
+
options.separator === true ? '[:-]' :
|
|
334
|
+
options.separator === false ? '[]?' : `\\${options.separator}`;
|
|
335
|
+
|
|
336
|
+
const regex = new RegExp(`^([\\[{\\(]?)[0-9A-F]{8}(${separator})[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');
|
|
332
337
|
|
|
333
338
|
return this.$_addRule({ name: 'guid', args: { options }, regex });
|
|
334
339
|
},
|
|
@@ -627,7 +632,7 @@ module.exports = Any.extend({
|
|
|
627
632
|
Common.assertOptions(options, ['allowRelative', 'allowQuerySquareBrackets', 'domain', 'relativeOnly', 'scheme']);
|
|
628
633
|
|
|
629
634
|
if (options.domain) {
|
|
630
|
-
Common.assertOptions(options.domain, ['allowUnicode', 'minDomainSegments', 'tlds']);
|
|
635
|
+
Common.assertOptions(options.domain, ['allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']);
|
|
631
636
|
}
|
|
632
637
|
|
|
633
638
|
const { regex, scheme } = Uri.regex(options);
|
|
@@ -642,11 +647,12 @@ module.exports = Any.extend({
|
|
|
642
647
|
|
|
643
648
|
const match = regex.exec(value);
|
|
644
649
|
if (match) {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
+
const matched = match[1] || match[2];
|
|
651
|
+
if (domain &&
|
|
652
|
+
(!options.allowRelative || matched) &&
|
|
653
|
+
!Domain.isValid(matched, domain)) {
|
|
654
|
+
|
|
655
|
+
return helpers.error('string.domain', { value: matched });
|
|
650
656
|
}
|
|
651
657
|
|
|
652
658
|
return value;
|
|
@@ -702,10 +708,10 @@ module.exports = Any.extend({
|
|
|
702
708
|
'string.min': '{{#label}} length must be at least {{#limit}} characters long',
|
|
703
709
|
'string.normalize': '{{#label}} must be unicode normalized in the {{#form}} form',
|
|
704
710
|
'string.token': '{{#label}} must only contain alpha-numeric and underscore characters',
|
|
705
|
-
'string.pattern.base': '{{#label}} with value
|
|
706
|
-
'string.pattern.name': '{{#label}} with value
|
|
707
|
-
'string.pattern.invert.base': '{{#label}} with value
|
|
708
|
-
'string.pattern.invert.name': '{{#label}} with value
|
|
711
|
+
'string.pattern.base': '{{#label}} with value {:[.]} fails to match the required pattern: {{#regex}}',
|
|
712
|
+
'string.pattern.name': '{{#label}} with value {:[.]} fails to match the {{#name}} pattern',
|
|
713
|
+
'string.pattern.invert.base': '{{#label}} with value {:[.]} matches the inverted pattern: {{#regex}}',
|
|
714
|
+
'string.pattern.invert.name': '{{#label}} with value {:[.]} matches the inverted {{#name}} pattern',
|
|
709
715
|
'string.trim': '{{#label}} must not have leading or trailing whitespace',
|
|
710
716
|
'string.uri': '{{#label}} must be a valid uri',
|
|
711
717
|
'string.uriCustomScheme': '{{#label}} must be a valid uri with a scheme matching the {{#scheme}} pattern',
|
|
@@ -728,6 +734,11 @@ internals.addressOptions = function (options) {
|
|
|
728
734
|
Assert(options.minDomainSegments === undefined ||
|
|
729
735
|
Number.isSafeInteger(options.minDomainSegments) && options.minDomainSegments > 0, 'minDomainSegments must be a positive integer');
|
|
730
736
|
|
|
737
|
+
// maxDomainSegments
|
|
738
|
+
|
|
739
|
+
Assert(options.maxDomainSegments === undefined ||
|
|
740
|
+
Number.isSafeInteger(options.maxDomainSegments) && options.maxDomainSegments > 0, 'maxDomainSegments must be a positive integer');
|
|
741
|
+
|
|
731
742
|
// tlds
|
|
732
743
|
|
|
733
744
|
if (options.tlds === false) {
|
|
@@ -751,6 +762,7 @@ internals.addressOptions = function (options) {
|
|
|
751
762
|
|
|
752
763
|
Assert(options.tlds.deny instanceof Set, 'tlds.deny must be an array, Set, or boolean');
|
|
753
764
|
Assert(!options.tlds.allow, 'Cannot specify both tlds.allow and tlds.deny lists');
|
|
765
|
+
internals.validateTlds(options.tlds.deny, 'tlds.deny');
|
|
754
766
|
return options;
|
|
755
767
|
}
|
|
756
768
|
|
|
@@ -769,16 +781,29 @@ internals.addressOptions = function (options) {
|
|
|
769
781
|
}
|
|
770
782
|
|
|
771
783
|
Assert(options.tlds.allow instanceof Set, 'tlds.allow must be an array, Set, or boolean');
|
|
784
|
+
internals.validateTlds(options.tlds.allow, 'tlds.allow');
|
|
772
785
|
return options;
|
|
773
786
|
};
|
|
774
787
|
|
|
775
788
|
|
|
789
|
+
internals.validateTlds = function (set, source) {
|
|
790
|
+
|
|
791
|
+
for (const tld of set) {
|
|
792
|
+
Assert(Domain.isValid(tld, { minDomainSegments: 1, maxDomainSegments: 1 }), `${source} must contain valid top level domain names`);
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
|
|
776
797
|
internals.isoDate = function (value) {
|
|
777
798
|
|
|
778
799
|
if (!Common.isIsoDate(value)) {
|
|
779
800
|
return null;
|
|
780
801
|
}
|
|
781
802
|
|
|
803
|
+
if (/.*T.*[+-]\d\d$/.test(value)) { // Add missing trailing zeros to timeshift
|
|
804
|
+
value += '00';
|
|
805
|
+
}
|
|
806
|
+
|
|
782
807
|
const date = new Date(value);
|
|
783
808
|
if (isNaN(date.getTime())) {
|
|
784
809
|
return null;
|