joi 14.3.0 → 17.2.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.
Files changed (59) hide show
  1. package/LICENSE.md +10 -0
  2. package/README.md +8 -118
  3. package/dist/joi-browser.min.js +1 -0
  4. package/lib/annotate.js +175 -0
  5. package/lib/base.js +1068 -0
  6. package/lib/cache.js +143 -0
  7. package/lib/common.js +216 -0
  8. package/lib/compile.js +283 -0
  9. package/lib/errors.js +160 -269
  10. package/lib/extend.js +312 -0
  11. package/lib/index.d.ts +2200 -0
  12. package/lib/index.js +179 -364
  13. package/lib/manifest.js +476 -0
  14. package/lib/messages.js +178 -0
  15. package/lib/modify.js +267 -0
  16. package/lib/ref.js +386 -25
  17. package/lib/schemas.js +291 -15
  18. package/lib/state.js +152 -0
  19. package/lib/template.js +427 -0
  20. package/lib/trace.js +346 -0
  21. package/lib/types/alternatives.js +329 -0
  22. package/lib/types/any.js +174 -0
  23. package/lib/types/array.js +775 -0
  24. package/lib/types/binary.js +98 -0
  25. package/lib/types/boolean.js +150 -0
  26. package/lib/types/date.js +233 -0
  27. package/lib/types/function.js +93 -0
  28. package/lib/types/keys.js +1043 -0
  29. package/lib/types/link.js +168 -0
  30. package/lib/types/number.js +335 -0
  31. package/lib/types/object.js +22 -0
  32. package/lib/types/string.js +820 -0
  33. package/lib/types/symbol.js +102 -0
  34. package/lib/validator.js +650 -0
  35. package/lib/values.js +263 -0
  36. package/package.json +34 -29
  37. package/CHANGELOG.md +0 -3
  38. package/LICENSE +0 -25
  39. package/lib/cast.js +0 -64
  40. package/lib/language.js +0 -166
  41. package/lib/set.js +0 -191
  42. package/lib/types/alternatives/index.js +0 -218
  43. package/lib/types/any/index.js +0 -978
  44. package/lib/types/any/settings.js +0 -36
  45. package/lib/types/array/index.js +0 -707
  46. package/lib/types/binary/index.js +0 -100
  47. package/lib/types/boolean/index.js +0 -100
  48. package/lib/types/date/index.js +0 -182
  49. package/lib/types/func/index.js +0 -90
  50. package/lib/types/lazy/index.js +0 -82
  51. package/lib/types/number/index.js +0 -248
  52. package/lib/types/object/index.js +0 -957
  53. package/lib/types/state.js +0 -11
  54. package/lib/types/string/index.js +0 -703
  55. package/lib/types/string/ip.js +0 -54
  56. package/lib/types/string/rfc3986.js +0 -219
  57. package/lib/types/string/uri.js +0 -46
  58. package/lib/types/symbol/index.js +0 -93
  59. package/lib/types/symbols.js +0 -5
package/lib/index.js CHANGED
@@ -1,467 +1,282 @@
1
1
  'use strict';
2
2
 
3
- // Load modules
3
+ const Assert = require('@hapi/hoek/lib/assert');
4
+ const Clone = require('@hapi/hoek/lib/clone');
4
5
 
5
- const Hoek = require('hoek');
6
- const Any = require('./types/any');
7
- const Cast = require('./cast');
6
+ const Cache = require('./cache');
7
+ const Common = require('./common');
8
+ const Compile = require('./compile');
8
9
  const Errors = require('./errors');
9
- const Lazy = require('./types/lazy');
10
+ const Extend = require('./extend');
11
+ const Manifest = require('./manifest');
10
12
  const Ref = require('./ref');
11
- const Settings = require('./types/any/settings');
13
+ const Template = require('./template');
14
+ const Trace = require('./trace');
12
15
 
16
+ let Schemas;
13
17
 
14
- // Declare internals
15
18
 
16
19
  const internals = {
17
- alternatives: require('./types/alternatives'),
18
- array: require('./types/array'),
19
- boolean: require('./types/boolean'),
20
- binary: require('./types/binary'),
21
- date: require('./types/date'),
22
- func: require('./types/func'),
23
- number: require('./types/number'),
24
- object: require('./types/object'),
25
- string: require('./types/string'),
26
- symbol: require('./types/symbol')
27
- };
28
-
29
- internals.callWithDefaults = function (schema, args) {
30
-
31
- Hoek.assert(this, 'Must be invoked on a Joi instance.');
32
-
33
- if (this._defaults) {
34
- schema = this._defaults(schema);
20
+ types: {
21
+ alternatives: require('./types/alternatives'),
22
+ any: require('./types/any'),
23
+ array: require('./types/array'),
24
+ boolean: require('./types/boolean'),
25
+ date: require('./types/date'),
26
+ function: require('./types/function'),
27
+ link: require('./types/link'),
28
+ number: require('./types/number'),
29
+ object: require('./types/object'),
30
+ string: require('./types/string'),
31
+ symbol: require('./types/symbol')
32
+ },
33
+ aliases: {
34
+ alt: 'alternatives',
35
+ bool: 'boolean',
36
+ func: 'function'
35
37
  }
36
-
37
- schema._currentJoi = this;
38
-
39
- return schema._init(...args);
40
38
  };
41
39
 
42
- internals.root = function () {
43
-
44
- const any = new Any();
45
-
46
- const root = any.clone();
47
- Any.prototype._currentJoi = root;
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']);
50
-
51
- root.any = function (...args) {
52
-
53
- Hoek.assert(args.length === 0, 'Joi.any() does not allow arguments.');
54
-
55
- return internals.callWithDefaults.call(this, any, args);
56
- };
57
-
58
- root.alternatives = root.alt = function (...args) {
59
-
60
- return internals.callWithDefaults.call(this, internals.alternatives, args);
61
- };
62
-
63
- root.array = function (...args) {
64
40
 
65
- Hoek.assert(args.length === 0, 'Joi.array() does not allow arguments.');
66
-
67
- return internals.callWithDefaults.call(this, internals.array, args);
68
- };
41
+ if (Buffer) { // $lab:coverage:ignore$
42
+ internals.types.binary = require('./types/binary');
43
+ }
69
44
 
70
- root.boolean = root.bool = function (...args) {
71
45
 
72
- Hoek.assert(args.length === 0, 'Joi.boolean() does not allow arguments.');
46
+ internals.root = function () {
73
47
 
74
- return internals.callWithDefaults.call(this, internals.boolean, args);
48
+ const root = {
49
+ _types: new Set(Object.keys(internals.types))
75
50
  };
76
51
 
77
- root.binary = function (...args) {
78
-
79
- Hoek.assert(args.length === 0, 'Joi.binary() does not allow arguments.');
80
-
81
- return internals.callWithDefaults.call(this, internals.binary, args);
82
- };
52
+ // Types
83
53
 
84
- root.date = function (...args) {
54
+ for (const type of root._types) {
55
+ root[type] = function (...args) {
85
56
 
86
- Hoek.assert(args.length === 0, 'Joi.date() does not allow arguments.');
57
+ Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');
58
+ return internals.generate(this, internals.types[type], args);
59
+ };
60
+ }
87
61
 
88
- return internals.callWithDefaults.call(this, internals.date, args);
89
- };
62
+ // Shortcuts
90
63
 
91
- root.func = function (...args) {
64
+ for (const method of ['allow', 'custom', 'disallow', 'equal', 'exist', 'forbidden', 'invalid', 'not', 'only', 'optional', 'options', 'prefs', 'preferences', 'required', 'strip', 'valid', 'when']) {
65
+ root[method] = function (...args) {
92
66
 
93
- Hoek.assert(args.length === 0, 'Joi.func() does not allow arguments.');
67
+ return this.any()[method](...args);
68
+ };
69
+ }
94
70
 
95
- return internals.callWithDefaults.call(this, internals.func, args);
96
- };
71
+ // Methods
97
72
 
98
- root.number = function (...args) {
73
+ Object.assign(root, internals.methods);
99
74
 
100
- Hoek.assert(args.length === 0, 'Joi.number() does not allow arguments.');
75
+ // Aliases
101
76
 
102
- return internals.callWithDefaults.call(this, internals.number, args);
103
- };
77
+ for (const alias in internals.aliases) {
78
+ const target = internals.aliases[alias];
79
+ root[alias] = root[target];
80
+ }
104
81
 
105
- root.object = function (...args) {
82
+ root.x = root.expression;
106
83
 
107
- return internals.callWithDefaults.call(this, internals.object, args);
108
- };
84
+ // Trace
109
85
 
110
- root.string = function (...args) {
86
+ if (Trace.setup) { // $lab:coverage:ignore$
87
+ Trace.setup(root);
88
+ }
111
89
 
112
- Hoek.assert(args.length === 0, 'Joi.string() does not allow arguments.');
90
+ return root;
91
+ };
113
92
 
114
- return internals.callWithDefaults.call(this, internals.string, args);
115
- };
116
93
 
117
- root.symbol = function (...args) {
94
+ internals.methods = {
118
95
 
119
- Hoek.assert(args.length === 0, 'Joi.symbol() does not allow arguments.');
96
+ ValidationError: Errors.ValidationError,
97
+ version: Common.version,
98
+ cache: Cache.provider,
120
99
 
121
- return internals.callWithDefaults.call(this, internals.symbol, args);
122
- };
100
+ assert(value, schema, ...args /* [message], [options] */) {
123
101
 
124
- root.ref = function (...args) {
102
+ internals.assert(value, schema, true, args);
103
+ },
125
104
 
126
- return Ref.create(...args);
127
- };
105
+ attempt(value, schema, ...args /* [message], [options] */) {
128
106
 
129
- root.isRef = function (ref) {
107
+ return internals.assert(value, schema, false, args);
108
+ },
130
109
 
131
- return Ref.isRef(ref);
132
- };
110
+ build(desc) {
133
111
 
134
- root.validate = function (value, ...args /*, [schema], [options], callback */) {
112
+ Assert(typeof Manifest.build === 'function', 'Manifest functionality disabled');
113
+ return Manifest.build(this, desc);
114
+ },
135
115
 
136
- const last = args[args.length - 1];
137
- const callback = typeof last === 'function' ? last : null;
116
+ checkPreferences(prefs) {
138
117
 
139
- const count = args.length - (callback ? 1 : 0);
140
- if (count === 0) {
141
- return any.validate(value, callback);
142
- }
118
+ Common.checkPreferences(prefs);
119
+ },
143
120
 
144
- const options = count === 2 ? args[1] : undefined;
145
- const schema = this.compile(args[0]);
121
+ compile(schema, options) {
146
122
 
147
- return schema._validateWithOptions(value, options, callback);
148
- };
123
+ return Compile.compile(this, schema, options);
124
+ },
149
125
 
150
- root.describe = function (...args) {
126
+ defaults(modifier) {
151
127
 
152
- const schema = args.length ? this.compile(args[0]) : any;
153
- return schema.describe();
154
- };
128
+ Assert(typeof modifier === 'function', 'modifier must be a function');
155
129
 
156
- root.compile = function (schema) {
130
+ const joi = Object.assign({}, this);
131
+ for (const type of joi._types) {
132
+ const schema = modifier(joi[type]());
133
+ Assert(Common.isSchema(schema), 'modifier must return a valid schema object');
157
134
 
158
- try {
159
- return Cast.schema(this, schema);
160
- }
161
- catch (err) {
162
- if (err.hasOwnProperty('path')) {
163
- err.message = err.message + '(' + err.path + ')';
164
- }
135
+ joi[type] = function (...args) {
165
136
 
166
- throw err;
137
+ return internals.generate(this, schema, args);
138
+ };
167
139
  }
168
- };
169
-
170
- root.assert = function (value, schema, message) {
171
-
172
- this.attempt(value, schema, message);
173
- };
174
-
175
- root.attempt = function (value, schema, message) {
176
-
177
- const result = this.validate(value, schema);
178
- const error = result.error;
179
- if (error) {
180
- if (!message) {
181
- if (typeof error.annotate === 'function') {
182
- error.message = error.annotate();
183
- }
184
140
 
185
- throw error;
186
- }
141
+ return joi;
142
+ },
187
143
 
188
- if (!(message instanceof Error)) {
189
- if (typeof error.annotate === 'function') {
190
- error.message = `${message} ${error.annotate()}`;
191
- }
144
+ expression(...args) {
192
145
 
193
- throw error;
194
- }
146
+ return new Template(...args);
147
+ },
195
148
 
196
- throw message;
197
- }
149
+ extend(...extensions) {
198
150
 
199
- return result.value;
200
- };
151
+ Common.verifyFlat(extensions, 'extend');
201
152
 
202
- root.reach = function (schema, path) {
153
+ Schemas = Schemas || require('./schemas');
203
154
 
204
- Hoek.assert(schema && schema instanceof Any, 'you must provide a joi schema');
205
- Hoek.assert(Array.isArray(path) || typeof path === 'string', 'path must be a string or an array of strings');
155
+ Assert(extensions.length, 'You need to provide at least one extension');
156
+ this.assert(extensions, Schemas.extensions);
206
157
 
207
- const reach = (sourceSchema, schemaPath) => {
158
+ const joi = Object.assign({}, this);
159
+ joi._types = new Set(joi._types);
208
160
 
209
- if (!schemaPath.length) {
210
- return sourceSchema;
211
- }
212
-
213
- const children = sourceSchema._inner.children;
214
- if (!children) {
215
- return;
216
- }
217
-
218
- const key = schemaPath.shift();
219
- for (let i = 0; i < children.length; ++i) {
220
- const child = children[i];
221
- if (child.key === key) {
222
- return reach(child.schema, schemaPath);
223
- }
161
+ for (let extension of extensions) {
162
+ if (typeof extension === 'function') {
163
+ extension = extension(joi);
224
164
  }
225
- };
226
165
 
227
- const schemaPath = typeof path === 'string' ? (path ? path.split('.') : []) : path.slice();
166
+ this.assert(extension, Schemas.extension);
228
167
 
229
- return reach(schema, schemaPath);
230
- };
168
+ const expanded = internals.expandExtension(extension, joi);
169
+ for (const item of expanded) {
170
+ Assert(joi[item.type] === undefined || joi._types.has(item.type), 'Cannot override name', item.type);
231
171
 
232
- root.lazy = function (...args) {
172
+ const base = item.base || this.any();
173
+ const schema = Extend.type(base, item);
233
174
 
234
- return internals.callWithDefaults.call(this, Lazy, args);
235
- };
175
+ joi._types.add(item.type);
176
+ joi[item.type] = function (...args) {
236
177
 
237
- root.defaults = function (fn) {
238
-
239
- Hoek.assert(typeof fn === 'function', 'Defaults must be a function');
240
-
241
- let joi = Object.create(this.any());
242
- joi = fn(joi);
243
-
244
- Hoek.assert(joi && joi instanceof this.constructor, 'defaults() must return a schema');
245
-
246
- Object.assign(joi, this, joi.clone()); // Re-add the types from `this` but also keep the settings from joi's potential new defaults
247
-
248
- joi._defaults = (schema) => {
249
-
250
- if (this._defaults) {
251
- schema = this._defaults(schema);
252
- Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema');
178
+ return internals.generate(this, schema, args);
179
+ };
253
180
  }
254
-
255
- schema = fn(schema);
256
- Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema');
257
- return schema;
258
- };
259
-
260
- return joi;
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
- });
181
+ }
271
182
 
272
183
  return joi;
273
- };
184
+ },
274
185
 
275
- root.extend = function (...args) {
186
+ isError: Errors.ValidationError.isError,
187
+ isExpression: Template.isTemplate,
188
+ isRef: Ref.isRef,
189
+ isSchema: Common.isSchema,
276
190
 
277
- const extensions = Hoek.flatten(args);
278
- Hoek.assert(extensions.length > 0, 'You need to provide at least one extension');
191
+ in(...args) {
279
192
 
280
- this.assert(extensions, root.extensionsSchema);
193
+ return Ref.in(...args);
194
+ },
281
195
 
282
- const joi = Object.create(this.any());
283
- Object.assign(joi, this);
284
- joi._currentJoi = joi;
285
- joi._binds = new Set(joi._binds);
196
+ override: Common.symbols.override,
286
197
 
287
- for (let i = 0; i < extensions.length; ++i) {
288
- let extension = extensions[i];
289
-
290
- if (typeof extension === 'function') {
291
- extension = extension(joi);
292
- }
198
+ ref(...args) {
293
199
 
294
- this.assert(extension, root.extensionSchema);
200
+ return Ref.create(...args);
201
+ },
295
202
 
296
- const base = (extension.base || this.any()).clone(); // Cloning because we're going to override language afterwards
297
- const ctor = base.constructor;
298
- const type = class extends ctor { // eslint-disable-line no-loop-func
203
+ types() {
299
204
 
300
- constructor() {
205
+ const types = {};
206
+ for (const type of this._types) {
207
+ types[type] = this[type]();
208
+ }
301
209
 
302
- super();
303
- if (extension.base) {
304
- Object.assign(this, base);
305
- }
210
+ for (const target in internals.aliases) {
211
+ types[target] = this[target]();
212
+ }
306
213
 
307
- this._type = extension.name;
214
+ return types;
215
+ }
216
+ };
308
217
 
309
- if (extension.language) {
310
- this._settings = Settings.concat(this._settings, {
311
- language: {
312
- [extension.name]: extension.language
313
- }
314
- });
315
- }
316
- }
317
218
 
318
- };
219
+ // Helpers
319
220
 
320
- if (extension.coerce) {
321
- type.prototype._coerce = function (value, state, options) {
221
+ internals.assert = function (value, schema, annotate, args /* [message], [options] */) {
322
222
 
323
- if (ctor.prototype._coerce) {
324
- const baseRet = ctor.prototype._coerce.call(this, value, state, options);
223
+ const message = args[0] instanceof Error || typeof args[0] === 'string' ? args[0] : null;
224
+ const options = message ? args[1] : args[0];
225
+ const result = schema.validate(value, Common.preferences({ errors: { stack: true } }, options || {}));
325
226
 
326
- if (baseRet.errors) {
327
- return baseRet;
328
- }
227
+ let error = result.error;
228
+ if (!error) {
229
+ return result.value;
230
+ }
329
231
 
330
- value = baseRet.value;
331
- }
232
+ if (message instanceof Error) {
233
+ throw message;
234
+ }
332
235
 
333
- const ret = extension.coerce.call(this, value, state, options);
334
- if (ret instanceof Errors.Err) {
335
- return { value, errors: ret };
336
- }
236
+ const display = annotate && typeof error.annotate === 'function' ? error.annotate() : error.message;
337
237
 
338
- return { value: ret };
339
- };
340
- }
238
+ if (error instanceof Errors.ValidationError === false) {
239
+ error = Clone(error);
240
+ }
341
241
 
342
- if (extension.pre) {
343
- type.prototype._base = function (value, state, options) {
242
+ error.message = message ? `${message} ${display}` : display;
243
+ throw error;
244
+ };
344
245
 
345
- if (ctor.prototype._base) {
346
- const baseRet = ctor.prototype._base.call(this, value, state, options);
347
246
 
348
- if (baseRet.errors) {
349
- return baseRet;
350
- }
247
+ internals.generate = function (root, schema, args) {
351
248
 
352
- value = baseRet.value;
353
- }
249
+ Assert(root, 'Must be invoked on a Joi instance.');
354
250
 
355
- const ret = extension.pre.call(this, value, state, options);
356
- if (ret instanceof Errors.Err) {
357
- return { value, errors: ret };
358
- }
251
+ schema.$_root = root;
359
252
 
360
- return { value: ret };
361
- };
362
- }
253
+ if (!schema._definition.args ||
254
+ !args.length) {
363
255
 
364
- if (extension.rules) {
365
- for (let j = 0; j < extension.rules.length; ++j) {
366
- const rule = extension.rules[j];
367
- const ruleArgs = rule.params ?
368
- (rule.params instanceof Any ? rule.params._inner.children.map((k) => k.key) : Object.keys(rule.params)) :
369
- [];
370
- const validateArgs = rule.params ? Cast.schema(this, rule.params) : null;
371
-
372
- type.prototype[rule.name] = function (...rArgs) { // eslint-disable-line no-loop-func
373
-
374
- if (rArgs.length > ruleArgs.length) {
375
- throw new Error('Unexpected number of arguments');
376
- }
377
-
378
- let hasRef = false;
379
- let arg = {};
380
-
381
- for (let k = 0; k < ruleArgs.length; ++k) {
382
- arg[ruleArgs[k]] = rArgs[k];
383
- if (!hasRef && Ref.isRef(rArgs[k])) {
384
- hasRef = true;
385
- }
386
- }
387
-
388
- if (validateArgs) {
389
- arg = joi.attempt(arg, validateArgs);
390
- }
391
-
392
- let schema;
393
- if (rule.validate) {
394
- const validate = function (value, state, options) {
395
-
396
- return rule.validate.call(this, arg, value, state, options);
397
- };
398
-
399
- schema = this._test(rule.name, arg, validate, {
400
- description: rule.description,
401
- hasRef
402
- });
403
- }
404
- else {
405
- schema = this.clone();
406
- }
407
-
408
- if (rule.setup) {
409
- const newSchema = rule.setup.call(schema, arg);
410
- if (newSchema !== undefined) {
411
- Hoek.assert(newSchema instanceof Any, `Setup of extension Joi.${this._type}().${rule.name}() must return undefined or a Joi object`);
412
- schema = newSchema;
413
- }
414
- }
415
-
416
- return schema;
417
- };
418
- }
419
- }
256
+ return schema;
257
+ }
420
258
 
421
- if (extension.describe) {
422
- type.prototype.describe = function () {
259
+ return schema._definition.args(schema, ...args);
260
+ };
423
261
 
424
- const description = ctor.prototype.describe.call(this);
425
- return extension.describe.call(this, description);
426
- };
427
- }
428
262
 
429
- const instance = new type();
430
- joi[extension.name] = function (...extArgs) {
263
+ internals.expandExtension = function (extension, joi) {
431
264
 
432
- return internals.callWithDefaults.call(this, instance, extArgs);
433
- };
265
+ if (typeof extension.type === 'string') {
266
+ return [extension];
267
+ }
434
268
 
435
- joi._binds.add(extension.name);
269
+ const extended = [];
270
+ for (const type of joi._types) {
271
+ if (extension.type.test(type)) {
272
+ const item = Object.assign({}, extension);
273
+ item.type = type;
274
+ item.base = joi[type]();
275
+ extended.push(item);
436
276
  }
277
+ }
437
278
 
438
- return joi;
439
- };
440
-
441
- root.extensionSchema = internals.object.keys({
442
- base: internals.object.type(Any, 'Joi object'),
443
- name: internals.string.required(),
444
- coerce: internals.func.arity(3),
445
- pre: internals.func.arity(3),
446
- language: internals.object,
447
- describe: internals.func.arity(1),
448
- rules: internals.array.items(internals.object.keys({
449
- name: internals.string.required(),
450
- setup: internals.func.arity(1),
451
- validate: internals.func.arity(4),
452
- params: [
453
- internals.object.pattern(/.*/, internals.object.type(Any, 'Joi object')),
454
- internals.object.type(internals.object.constructor, 'Joi object')
455
- ],
456
- description: [internals.string, internals.func.arity(1)]
457
- }).or('setup', 'validate'))
458
- }).strict();
459
-
460
- root.extensionsSchema = internals.array.items([internals.object, internals.func.arity(1)]).strict();
461
-
462
- root.version = require('../package.json').version;
463
-
464
- return root;
279
+ return extended;
465
280
  };
466
281
 
467
282