joi 14.2.0 → 17.2.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.
Files changed (59) hide show
  1. package/LICENSE.md +10 -0
  2. package/README.md +9 -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 -347
  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
@@ -0,0 +1,1043 @@
1
+ 'use strict';
2
+
3
+ const ApplyToDefaults = require('@hapi/hoek/lib/applyToDefaults');
4
+ const Assert = require('@hapi/hoek/lib/assert');
5
+ const Clone = require('@hapi/hoek/lib/clone');
6
+ const Topo = require('@hapi/topo');
7
+
8
+ const Any = require('./any');
9
+ const Common = require('../common');
10
+ const Compile = require('../compile');
11
+ const Errors = require('../errors');
12
+ const Ref = require('../ref');
13
+ const Template = require('../template');
14
+
15
+
16
+ const internals = {
17
+ renameDefaults: {
18
+ alias: false, // Keep old value in place
19
+ multiple: false, // Allow renaming multiple keys into the same target
20
+ override: false // Overrides an existing key
21
+ }
22
+ };
23
+
24
+
25
+ module.exports = Any.extend({
26
+
27
+ type: '_keys',
28
+
29
+ properties: {
30
+
31
+ typeof: 'object'
32
+ },
33
+
34
+ flags: {
35
+
36
+ unknown: { default: false }
37
+ },
38
+
39
+ terms: {
40
+
41
+ dependencies: { init: null },
42
+ keys: { init: null, manifest: { mapped: { from: 'schema', to: 'key' } } },
43
+ patterns: { init: null },
44
+ renames: { init: null }
45
+ },
46
+
47
+ args(schema, keys) {
48
+
49
+ return schema.keys(keys);
50
+ },
51
+
52
+ validate(value, { schema, error, state, prefs }) {
53
+
54
+ if (!value ||
55
+ typeof value !== schema.$_property('typeof') ||
56
+ Array.isArray(value)) {
57
+
58
+ return { value, errors: error('object.base', { type: schema.$_property('typeof') }) };
59
+ }
60
+
61
+ // Skip if there are no other rules to test
62
+
63
+ if (!schema.$_terms.renames &&
64
+ !schema.$_terms.dependencies &&
65
+ !schema.$_terms.keys && // null allows any keys
66
+ !schema.$_terms.patterns &&
67
+ !schema.$_terms.externals) {
68
+
69
+ return;
70
+ }
71
+
72
+ // Shallow clone value
73
+
74
+ value = internals.clone(value, prefs);
75
+ const errors = [];
76
+
77
+ // Rename keys
78
+
79
+ if (schema.$_terms.renames &&
80
+ !internals.rename(schema, value, state, prefs, errors)) {
81
+
82
+ return { value, errors };
83
+ }
84
+
85
+ // Anything allowed
86
+
87
+ if (!schema.$_terms.keys && // null allows any keys
88
+ !schema.$_terms.patterns &&
89
+ !schema.$_terms.dependencies) {
90
+
91
+ return { value, errors };
92
+ }
93
+
94
+ // Defined keys
95
+
96
+ const unprocessed = new Set(Object.keys(value));
97
+
98
+ if (schema.$_terms.keys) {
99
+ const ancestors = [value, ...state.ancestors];
100
+
101
+ for (const child of schema.$_terms.keys) {
102
+ const key = child.key;
103
+ const item = value[key];
104
+
105
+ unprocessed.delete(key);
106
+
107
+ const localState = state.localize([...state.path, key], ancestors, child);
108
+ const result = child.schema.$_validate(item, localState, prefs);
109
+
110
+ if (result.errors) {
111
+ if (prefs.abortEarly) {
112
+ return { value, errors: result.errors };
113
+ }
114
+
115
+ errors.push(...result.errors);
116
+ }
117
+ else if (child.schema._flags.result === 'strip' ||
118
+ result.value === undefined && item !== undefined) {
119
+
120
+ delete value[key];
121
+ }
122
+ else if (result.value !== undefined) {
123
+ value[key] = result.value;
124
+ }
125
+ }
126
+ }
127
+
128
+ // Unknown keys
129
+
130
+ if (unprocessed.size ||
131
+ schema._flags._hasPatternMatch) {
132
+
133
+ const early = internals.unknown(schema, value, unprocessed, errors, state, prefs);
134
+ if (early) {
135
+ return early;
136
+ }
137
+ }
138
+
139
+ // Validate dependencies
140
+
141
+ if (schema.$_terms.dependencies) {
142
+ for (const dep of schema.$_terms.dependencies) {
143
+ if (dep.key &&
144
+ dep.key.resolve(value, state, prefs, null, { shadow: false }) === undefined) {
145
+
146
+ continue;
147
+ }
148
+
149
+ const failed = internals.dependencies[dep.rel](schema, dep, value, state, prefs);
150
+ if (failed) {
151
+ const report = schema.$_createError(failed.code, value, failed.context, state, prefs);
152
+ if (prefs.abortEarly) {
153
+ return { value, errors: report };
154
+ }
155
+
156
+ errors.push(report);
157
+ }
158
+ }
159
+ }
160
+
161
+ return { value, errors };
162
+ },
163
+
164
+ rules: {
165
+
166
+ and: {
167
+ method(...peers /*, [options] */) {
168
+
169
+ Common.verifyFlat(peers, 'and');
170
+
171
+ return internals.dependency(this, 'and', null, peers);
172
+ }
173
+ },
174
+
175
+ append: {
176
+ method(schema) {
177
+
178
+ if (schema === null ||
179
+ schema === undefined ||
180
+ Object.keys(schema).length === 0) {
181
+
182
+ return this;
183
+ }
184
+
185
+ return this.keys(schema);
186
+ }
187
+ },
188
+
189
+ assert: {
190
+ method(subject, schema, message) {
191
+
192
+ if (!Template.isTemplate(subject)) {
193
+ subject = Compile.ref(subject);
194
+ }
195
+
196
+ Assert(message === undefined || typeof message === 'string', 'Message must be a string');
197
+
198
+ schema = this.$_compile(schema, { appendPath: true });
199
+
200
+ const obj = this.$_addRule({ name: 'assert', args: { subject, schema, message } });
201
+ obj.$_mutateRegister(subject);
202
+ obj.$_mutateRegister(schema);
203
+ return obj;
204
+ },
205
+ validate(value, { error, prefs, state }, { subject, schema, message }) {
206
+
207
+ const about = subject.resolve(value, state, prefs);
208
+ const path = Ref.isRef(subject) ? subject.absolute(state) : [];
209
+ if (schema.$_match(about, state.localize(path, [value, ...state.ancestors], schema), prefs)) {
210
+ return value;
211
+ }
212
+
213
+ return error('object.assert', { subject, message });
214
+ },
215
+ args: ['subject', 'schema', 'message'],
216
+ multi: true
217
+ },
218
+
219
+ instance: {
220
+ method(constructor, name) {
221
+
222
+ Assert(typeof constructor === 'function', 'constructor must be a function');
223
+
224
+ name = name || constructor.name;
225
+
226
+ return this.$_addRule({ name: 'instance', args: { constructor, name } });
227
+ },
228
+ validate(value, helpers, { constructor, name }) {
229
+
230
+ if (value instanceof constructor) {
231
+ return value;
232
+ }
233
+
234
+ return helpers.error('object.instance', { type: name, value });
235
+ },
236
+ args: ['constructor', 'name']
237
+ },
238
+
239
+ keys: {
240
+ method(schema) {
241
+
242
+ Assert(schema === undefined || typeof schema === 'object', 'Object schema must be a valid object');
243
+ Assert(!Common.isSchema(schema), 'Object schema cannot be a joi schema');
244
+
245
+ const obj = this.clone();
246
+
247
+ if (!schema) { // Allow all
248
+ obj.$_terms.keys = null;
249
+ }
250
+ else if (!Object.keys(schema).length) { // Allow none
251
+ obj.$_terms.keys = new internals.Keys();
252
+ }
253
+ else {
254
+ obj.$_terms.keys = obj.$_terms.keys ? obj.$_terms.keys.filter((child) => !schema.hasOwnProperty(child.key)) : new internals.Keys();
255
+ for (const key in schema) {
256
+ Common.tryWithPath(() => obj.$_terms.keys.push({ key, schema: this.$_compile(schema[key]) }), key);
257
+ }
258
+ }
259
+
260
+ return obj.$_mutateRebuild();
261
+ }
262
+ },
263
+
264
+ length: {
265
+ method(limit) {
266
+
267
+ return this.$_addRule({ name: 'length', args: { limit }, operator: '=' });
268
+ },
269
+ validate(value, helpers, { limit }, { name, operator, args }) {
270
+
271
+ if (Common.compare(Object.keys(value).length, limit, operator)) {
272
+ return value;
273
+ }
274
+
275
+ return helpers.error('object.' + name, { limit: args.limit, value });
276
+ },
277
+ args: [
278
+ {
279
+ name: 'limit',
280
+ ref: true,
281
+ assert: Common.limit,
282
+ message: 'must be a positive integer'
283
+ }
284
+ ]
285
+ },
286
+
287
+ max: {
288
+ method(limit) {
289
+
290
+ return this.$_addRule({ name: 'max', method: 'length', args: { limit }, operator: '<=' });
291
+ }
292
+ },
293
+
294
+ min: {
295
+ method(limit) {
296
+
297
+ return this.$_addRule({ name: 'min', method: 'length', args: { limit }, operator: '>=' });
298
+ }
299
+ },
300
+
301
+ nand: {
302
+ method(...peers /*, [options] */) {
303
+
304
+ Common.verifyFlat(peers, 'nand');
305
+
306
+ return internals.dependency(this, 'nand', null, peers);
307
+ }
308
+ },
309
+
310
+ or: {
311
+ method(...peers /*, [options] */) {
312
+
313
+ Common.verifyFlat(peers, 'or');
314
+
315
+ return internals.dependency(this, 'or', null, peers);
316
+ }
317
+ },
318
+
319
+ oxor: {
320
+ method(...peers /*, [options] */) {
321
+
322
+ return internals.dependency(this, 'oxor', null, peers);
323
+ }
324
+ },
325
+
326
+ pattern: {
327
+ method(pattern, schema, options = {}) {
328
+
329
+ const isRegExp = pattern instanceof RegExp;
330
+ if (!isRegExp) {
331
+ pattern = this.$_compile(pattern, { appendPath: true });
332
+ }
333
+
334
+ Assert(schema !== undefined, 'Invalid rule');
335
+ Common.assertOptions(options, ['fallthrough', 'matches']);
336
+
337
+ if (isRegExp) {
338
+ Assert(!pattern.flags.includes('g') && !pattern.flags.includes('y'), 'pattern should not use global or sticky mode');
339
+ }
340
+
341
+ schema = this.$_compile(schema, { appendPath: true });
342
+
343
+ const obj = this.clone();
344
+ obj.$_terms.patterns = obj.$_terms.patterns || [];
345
+ const config = { [isRegExp ? 'regex' : 'schema']: pattern, rule: schema };
346
+ if (options.matches) {
347
+ config.matches = this.$_compile(options.matches);
348
+ if (config.matches.type !== 'array') {
349
+ config.matches = config.matches.$_root.array().items(config.matches);
350
+ }
351
+
352
+ obj.$_mutateRegister(config.matches);
353
+ obj.$_setFlag('_hasPatternMatch', true, { clone: false });
354
+ }
355
+
356
+ if (options.fallthrough) {
357
+ config.fallthrough = true;
358
+ }
359
+
360
+ obj.$_terms.patterns.push(config);
361
+ obj.$_mutateRegister(schema);
362
+ return obj;
363
+ }
364
+ },
365
+
366
+ ref: {
367
+ method() {
368
+
369
+ return this.$_addRule('ref');
370
+ },
371
+ validate(value, helpers) {
372
+
373
+ if (Ref.isRef(value)) {
374
+ return value;
375
+ }
376
+
377
+ return helpers.error('object.refType', { value });
378
+ }
379
+ },
380
+
381
+ regex: {
382
+ method() {
383
+
384
+ return this.$_addRule('regex');
385
+ },
386
+ validate(value, helpers) {
387
+
388
+ if (value instanceof RegExp) {
389
+ return value;
390
+ }
391
+
392
+ return helpers.error('object.regex', { value });
393
+ }
394
+ },
395
+
396
+ rename: {
397
+ method(from, to, options = {}) {
398
+
399
+ Assert(typeof from === 'string' || from instanceof RegExp, 'Rename missing the from argument');
400
+ Assert(typeof to === 'string' || to instanceof Template, 'Invalid rename to argument');
401
+ Assert(to !== from, 'Cannot rename key to same name:', from);
402
+
403
+ Common.assertOptions(options, ['alias', 'ignoreUndefined', 'override', 'multiple']);
404
+
405
+ const obj = this.clone();
406
+
407
+ obj.$_terms.renames = obj.$_terms.renames || [];
408
+ for (const rename of obj.$_terms.renames) {
409
+ Assert(rename.from !== from, 'Cannot rename the same key multiple times');
410
+ }
411
+
412
+ if (to instanceof Template) {
413
+ obj.$_mutateRegister(to);
414
+ }
415
+
416
+ obj.$_terms.renames.push({
417
+ from,
418
+ to,
419
+ options: ApplyToDefaults(internals.renameDefaults, options)
420
+ });
421
+
422
+ return obj;
423
+ }
424
+ },
425
+
426
+ schema: {
427
+ method(type = 'any') {
428
+
429
+ return this.$_addRule({ name: 'schema', args: { type } });
430
+ },
431
+ validate(value, helpers, { type }) {
432
+
433
+ if (Common.isSchema(value) &&
434
+ (type === 'any' || value.type === type)) {
435
+
436
+ return value;
437
+ }
438
+
439
+ return helpers.error('object.schema', { type });
440
+ }
441
+ },
442
+
443
+ unknown: {
444
+ method(allow) {
445
+
446
+ return this.$_setFlag('unknown', allow !== false);
447
+ }
448
+ },
449
+
450
+ with: {
451
+ method(key, peers, options = {}) {
452
+
453
+ return internals.dependency(this, 'with', key, peers, options);
454
+ }
455
+ },
456
+
457
+ without: {
458
+ method(key, peers, options = {}) {
459
+
460
+ return internals.dependency(this, 'without', key, peers, options);
461
+ }
462
+ },
463
+
464
+ xor: {
465
+ method(...peers /*, [options] */) {
466
+
467
+ Common.verifyFlat(peers, 'xor');
468
+
469
+ return internals.dependency(this, 'xor', null, peers);
470
+ }
471
+ }
472
+ },
473
+
474
+ overrides: {
475
+
476
+ default(value, options) {
477
+
478
+ if (value === undefined) {
479
+ value = Common.symbols.deepDefault;
480
+ }
481
+
482
+ return this.$_parent('default', value, options);
483
+ }
484
+ },
485
+
486
+ rebuild(schema) {
487
+
488
+ if (schema.$_terms.keys) {
489
+ const topo = new Topo.Sorter();
490
+ for (const child of schema.$_terms.keys) {
491
+ Common.tryWithPath(() => topo.add(child, { after: child.schema.$_rootReferences(), group: child.key }), child.key);
492
+ }
493
+
494
+ schema.$_terms.keys = new internals.Keys(...topo.nodes);
495
+ }
496
+ },
497
+
498
+ manifest: {
499
+
500
+ build(obj, desc) {
501
+
502
+ if (desc.keys) {
503
+ obj = obj.keys(desc.keys);
504
+ }
505
+
506
+ if (desc.dependencies) {
507
+ for (const { rel, key = null, peers, options } of desc.dependencies) {
508
+ obj = internals.dependency(obj, rel, key, peers, options);
509
+ }
510
+ }
511
+
512
+ if (desc.patterns) {
513
+ for (const { regex, schema, rule, fallthrough, matches } of desc.patterns) {
514
+ obj = obj.pattern(regex || schema, rule, { fallthrough, matches });
515
+ }
516
+ }
517
+
518
+ if (desc.renames) {
519
+ for (const { from, to, options } of desc.renames) {
520
+ obj = obj.rename(from, to, options);
521
+ }
522
+ }
523
+
524
+ return obj;
525
+ }
526
+ },
527
+
528
+ messages: {
529
+ 'object.and': '{{#label}} contains {{#presentWithLabels}} without its required peers {{#missingWithLabels}}',
530
+ 'object.assert': '{{#label}} is invalid because {if(#subject.key, `"` + #subject.key + `" failed to ` + (#message || "pass the assertion test"), #message || "the assertion failed")}',
531
+ 'object.base': '{{#label}} must be of type {{#type}}',
532
+ 'object.instance': '{{#label}} must be an instance of {{:#type}}',
533
+ 'object.length': '{{#label}} must have {{#limit}} key{if(#limit == 1, "", "s")}',
534
+ 'object.max': '{{#label}} must have less than or equal to {{#limit}} key{if(#limit == 1, "", "s")}',
535
+ 'object.min': '{{#label}} must have at least {{#limit}} key{if(#limit == 1, "", "s")}',
536
+ 'object.missing': '{{#label}} must contain at least one of {{#peersWithLabels}}',
537
+ 'object.nand': '{{:#mainWithLabel}} must not exist simultaneously with {{#peersWithLabels}}',
538
+ 'object.oxor': '{{#label}} contains a conflict between optional exclusive peers {{#peersWithLabels}}',
539
+ 'object.pattern.match': '{{#label}} keys failed to match pattern requirements',
540
+ 'object.refType': '{{#label}} must be a Joi reference',
541
+ 'object.regex': '{{#label}} must be a RegExp object',
542
+ 'object.rename.multiple': '{{#label}} cannot rename {{:#from}} because multiple renames are disabled and another key was already renamed to {{:#to}}',
543
+ 'object.rename.override': '{{#label}} cannot rename {{:#from}} because override is disabled and target {{:#to}} exists',
544
+ 'object.schema': '{{#label}} must be a Joi schema of {{#type}} type',
545
+ 'object.unknown': '{{#label}} is not allowed',
546
+ 'object.with': '{{:#mainWithLabel}} missing required peer {{:#peerWithLabel}}',
547
+ 'object.without': '{{:#mainWithLabel}} conflict with forbidden peer {{:#peerWithLabel}}',
548
+ 'object.xor': '{{#label}} contains a conflict between exclusive peers {{#peersWithLabels}}'
549
+ }
550
+ });
551
+
552
+
553
+ // Helpers
554
+
555
+ internals.clone = function (value, prefs) {
556
+
557
+ // Object
558
+
559
+ if (typeof value === 'object') {
560
+ if (prefs.nonEnumerables) {
561
+ return Clone(value, { shallow: true });
562
+ }
563
+
564
+ const clone = Object.create(Object.getPrototypeOf(value));
565
+ Object.assign(clone, value);
566
+ return clone;
567
+ }
568
+
569
+ // Function
570
+
571
+ const clone = function (...args) {
572
+
573
+ return value.apply(this, args);
574
+ };
575
+
576
+ clone.prototype = Clone(value.prototype);
577
+ Object.defineProperty(clone, 'name', { value: value.name, writable: false });
578
+ Object.defineProperty(clone, 'length', { value: value.length, writable: false });
579
+ Object.assign(clone, value);
580
+ return clone;
581
+ };
582
+
583
+
584
+ internals.dependency = function (schema, rel, key, peers, options) {
585
+
586
+ Assert(key === null || typeof key === 'string', rel, 'key must be a strings');
587
+
588
+ // Extract options from peers array
589
+
590
+ if (!options) {
591
+ options = peers.length > 1 && typeof peers[peers.length - 1] === 'object' ? peers.pop() : {};
592
+ }
593
+
594
+ Common.assertOptions(options, ['separator']);
595
+
596
+ peers = [].concat(peers);
597
+
598
+ // Cast peer paths
599
+
600
+ const separator = Common.default(options.separator, '.');
601
+ const paths = [];
602
+ for (const peer of peers) {
603
+ Assert(typeof peer === 'string', rel, 'peers must be a string or a reference');
604
+ paths.push(Compile.ref(peer, { separator, ancestor: 0, prefix: false }));
605
+ }
606
+
607
+ // Cast key
608
+
609
+ if (key !== null) {
610
+ key = Compile.ref(key, { separator, ancestor: 0, prefix: false });
611
+ }
612
+
613
+ // Add rule
614
+
615
+ const obj = schema.clone();
616
+ obj.$_terms.dependencies = obj.$_terms.dependencies || [];
617
+ obj.$_terms.dependencies.push(new internals.Dependency(rel, key, paths, peers));
618
+ return obj;
619
+ };
620
+
621
+
622
+ internals.dependencies = {
623
+
624
+ and(schema, dep, value, state, prefs) {
625
+
626
+ const missing = [];
627
+ const present = [];
628
+ const count = dep.peers.length;
629
+ for (const peer of dep.peers) {
630
+ if (peer.resolve(value, state, prefs, null, { shadow: false }) === undefined) {
631
+ missing.push(peer.key);
632
+ }
633
+ else {
634
+ present.push(peer.key);
635
+ }
636
+ }
637
+
638
+ if (missing.length !== count &&
639
+ present.length !== count) {
640
+
641
+ return {
642
+ code: 'object.and',
643
+ context: {
644
+ present,
645
+ presentWithLabels: internals.keysToLabels(schema, present),
646
+ missing,
647
+ missingWithLabels: internals.keysToLabels(schema, missing)
648
+ }
649
+ };
650
+ }
651
+ },
652
+
653
+ nand(schema, dep, value, state, prefs) {
654
+
655
+ const present = [];
656
+ for (const peer of dep.peers) {
657
+ if (peer.resolve(value, state, prefs, null, { shadow: false }) !== undefined) {
658
+ present.push(peer.key);
659
+ }
660
+ }
661
+
662
+ if (present.length !== dep.peers.length) {
663
+ return;
664
+ }
665
+
666
+ const main = dep.paths[0];
667
+ const values = dep.paths.slice(1);
668
+ return {
669
+ code: 'object.nand',
670
+ context: {
671
+ main,
672
+ mainWithLabel: internals.keysToLabels(schema, main),
673
+ peers: values,
674
+ peersWithLabels: internals.keysToLabels(schema, values)
675
+ }
676
+ };
677
+ },
678
+
679
+ or(schema, dep, value, state, prefs) {
680
+
681
+ for (const peer of dep.peers) {
682
+ if (peer.resolve(value, state, prefs, null, { shadow: false }) !== undefined) {
683
+ return;
684
+ }
685
+ }
686
+
687
+ return {
688
+ code: 'object.missing',
689
+ context: {
690
+ peers: dep.paths,
691
+ peersWithLabels: internals.keysToLabels(schema, dep.paths)
692
+ }
693
+ };
694
+ },
695
+
696
+ oxor(schema, dep, value, state, prefs) {
697
+
698
+ const present = [];
699
+ for (const peer of dep.peers) {
700
+ if (peer.resolve(value, state, prefs, null, { shadow: false }) !== undefined) {
701
+ present.push(peer.key);
702
+ }
703
+ }
704
+
705
+ if (!present.length ||
706
+ present.length === 1) {
707
+
708
+ return;
709
+ }
710
+
711
+ const context = { peers: dep.paths, peersWithLabels: internals.keysToLabels(schema, dep.paths) };
712
+ context.present = present;
713
+ context.presentWithLabels = internals.keysToLabels(schema, present);
714
+ return { code: 'object.oxor', context };
715
+ },
716
+
717
+ with(schema, dep, value, state, prefs) {
718
+
719
+ for (const peer of dep.peers) {
720
+ if (peer.resolve(value, state, prefs, null, { shadow: false }) === undefined) {
721
+ return {
722
+ code: 'object.with',
723
+ context: {
724
+ main: dep.key.key,
725
+ mainWithLabel: internals.keysToLabels(schema, dep.key.key),
726
+ peer: peer.key,
727
+ peerWithLabel: internals.keysToLabels(schema, peer.key)
728
+ }
729
+ };
730
+ }
731
+ }
732
+ },
733
+
734
+ without(schema, dep, value, state, prefs) {
735
+
736
+ for (const peer of dep.peers) {
737
+ if (peer.resolve(value, state, prefs, null, { shadow: false }) !== undefined) {
738
+ return {
739
+ code: 'object.without',
740
+ context: {
741
+ main: dep.key.key,
742
+ mainWithLabel: internals.keysToLabels(schema, dep.key.key),
743
+ peer: peer.key,
744
+ peerWithLabel: internals.keysToLabels(schema, peer.key)
745
+ }
746
+ };
747
+ }
748
+ }
749
+ },
750
+
751
+ xor(schema, dep, value, state, prefs) {
752
+
753
+ const present = [];
754
+ for (const peer of dep.peers) {
755
+ if (peer.resolve(value, state, prefs, null, { shadow: false }) !== undefined) {
756
+ present.push(peer.key);
757
+ }
758
+ }
759
+
760
+ if (present.length === 1) {
761
+ return;
762
+ }
763
+
764
+ const context = { peers: dep.paths, peersWithLabels: internals.keysToLabels(schema, dep.paths) };
765
+ if (present.length === 0) {
766
+ return { code: 'object.missing', context };
767
+ }
768
+
769
+ context.present = present;
770
+ context.presentWithLabels = internals.keysToLabels(schema, present);
771
+ return { code: 'object.xor', context };
772
+ }
773
+ };
774
+
775
+
776
+ internals.keysToLabels = function (schema, keys) {
777
+
778
+ if (Array.isArray(keys)) {
779
+ return keys.map((key) => schema.$_mapLabels(key));
780
+ }
781
+
782
+ return schema.$_mapLabels(keys);
783
+ };
784
+
785
+
786
+ internals.rename = function (schema, value, state, prefs, errors) {
787
+
788
+ const renamed = {};
789
+ for (const rename of schema.$_terms.renames) {
790
+ const matches = [];
791
+ const pattern = typeof rename.from !== 'string';
792
+
793
+ if (!pattern) {
794
+ if (Object.prototype.hasOwnProperty.call(value, rename.from) &&
795
+ (value[rename.from] !== undefined || !rename.options.ignoreUndefined)) {
796
+
797
+ matches.push(rename);
798
+ }
799
+ }
800
+ else {
801
+ for (const from in value) {
802
+ if (value[from] === undefined &&
803
+ rename.options.ignoreUndefined) {
804
+
805
+ continue;
806
+ }
807
+
808
+ if (from === rename.to) {
809
+ continue;
810
+ }
811
+
812
+ const match = rename.from.exec(from);
813
+ if (!match) {
814
+ continue;
815
+ }
816
+
817
+ matches.push({ from, to: rename.to, match });
818
+ }
819
+ }
820
+
821
+ for (const match of matches) {
822
+ const from = match.from;
823
+ let to = match.to;
824
+ if (to instanceof Template) {
825
+ to = to.render(value, state, prefs, match.match);
826
+ }
827
+
828
+ if (from === to) {
829
+ continue;
830
+ }
831
+
832
+ if (!rename.options.multiple &&
833
+ renamed[to]) {
834
+
835
+ errors.push(schema.$_createError('object.rename.multiple', value, { from, to, pattern }, state, prefs));
836
+ if (prefs.abortEarly) {
837
+ return false;
838
+ }
839
+ }
840
+
841
+ if (Object.prototype.hasOwnProperty.call(value, to) &&
842
+ !rename.options.override &&
843
+ !renamed[to]) {
844
+
845
+ errors.push(schema.$_createError('object.rename.override', value, { from, to, pattern }, state, prefs));
846
+ if (prefs.abortEarly) {
847
+ return false;
848
+ }
849
+ }
850
+
851
+ if (value[from] === undefined) {
852
+ delete value[to];
853
+ }
854
+ else {
855
+ value[to] = value[from];
856
+ }
857
+
858
+ renamed[to] = true;
859
+
860
+ if (!rename.options.alias) {
861
+ delete value[from];
862
+ }
863
+ }
864
+ }
865
+
866
+ return true;
867
+ };
868
+
869
+
870
+ internals.unknown = function (schema, value, unprocessed, errors, state, prefs) {
871
+
872
+ if (schema.$_terms.patterns) {
873
+ let hasMatches = false;
874
+ const matches = schema.$_terms.patterns.map((pattern) => {
875
+
876
+ if (pattern.matches) {
877
+ hasMatches = true;
878
+ return [];
879
+ }
880
+ });
881
+
882
+ const ancestors = [value, ...state.ancestors];
883
+
884
+ for (const key of unprocessed) {
885
+ const item = value[key];
886
+ const path = [...state.path, key];
887
+
888
+ for (let i = 0; i < schema.$_terms.patterns.length; ++i) {
889
+ const pattern = schema.$_terms.patterns[i];
890
+ if (pattern.regex) {
891
+ const match = pattern.regex.test(key);
892
+ state.mainstay.tracer.debug(state, 'rule', `pattern.${i}`, match ? 'pass' : 'error');
893
+ if (!match) {
894
+ continue;
895
+ }
896
+ }
897
+ else {
898
+ if (!pattern.schema.$_match(key, state.nest(pattern.schema, `pattern.${i}`), prefs)) {
899
+ continue;
900
+ }
901
+ }
902
+
903
+ unprocessed.delete(key);
904
+
905
+ const localState = state.localize(path, ancestors, { schema: pattern.rule, key });
906
+ const result = pattern.rule.$_validate(item, localState, prefs);
907
+ if (result.errors) {
908
+ if (prefs.abortEarly) {
909
+ return { value, errors: result.errors };
910
+ }
911
+
912
+ errors.push(...result.errors);
913
+ }
914
+
915
+ if (pattern.matches) {
916
+ matches[i].push(key);
917
+ }
918
+
919
+ value[key] = result.value;
920
+ if (!pattern.fallthrough) {
921
+ break;
922
+ }
923
+ }
924
+ }
925
+
926
+ // Validate pattern matches rules
927
+
928
+ if (hasMatches) {
929
+ for (let i = 0; i < matches.length; ++i) {
930
+ const match = matches[i];
931
+ if (!match) {
932
+ continue;
933
+ }
934
+
935
+ const stpm = schema.$_terms.patterns[i].matches;
936
+ const localState = state.localize(state.path, ancestors, stpm);
937
+ const result = stpm.$_validate(match, localState, prefs);
938
+ if (result.errors) {
939
+ const details = Errors.details(result.errors, { override: false });
940
+ details.matches = match;
941
+ const report = schema.$_createError('object.pattern.match', value, details, state, prefs);
942
+ if (prefs.abortEarly) {
943
+ return { value, errors: report };
944
+ }
945
+
946
+ errors.push(report);
947
+ }
948
+ }
949
+ }
950
+ }
951
+
952
+ if (!unprocessed.size ||
953
+ !schema.$_terms.keys && !schema.$_terms.patterns) { // If no keys or patterns specified, unknown keys allowed
954
+
955
+ return;
956
+ }
957
+
958
+ if (prefs.stripUnknown && !schema._flags.unknown ||
959
+ prefs.skipFunctions) {
960
+
961
+ const stripUnknown = prefs.stripUnknown ? (prefs.stripUnknown === true ? true : !!prefs.stripUnknown.objects) : false;
962
+
963
+ for (const key of unprocessed) {
964
+ if (stripUnknown) {
965
+ delete value[key];
966
+ unprocessed.delete(key);
967
+ }
968
+ else if (typeof value[key] === 'function') {
969
+ unprocessed.delete(key);
970
+ }
971
+ }
972
+ }
973
+
974
+ const forbidUnknown = !Common.default(schema._flags.unknown, prefs.allowUnknown);
975
+ if (forbidUnknown) {
976
+ for (const unprocessedKey of unprocessed) {
977
+ const localState = state.localize([...state.path, unprocessedKey], []);
978
+ const report = schema.$_createError('object.unknown', value[unprocessedKey], { child: unprocessedKey }, localState, prefs, { flags: false });
979
+ if (prefs.abortEarly) {
980
+ return { value, errors: report };
981
+ }
982
+
983
+ errors.push(report);
984
+ }
985
+ }
986
+ };
987
+
988
+
989
+ internals.Dependency = class {
990
+
991
+ constructor(rel, key, peers, paths) {
992
+
993
+ this.rel = rel;
994
+ this.key = key;
995
+ this.peers = peers;
996
+ this.paths = paths;
997
+ }
998
+
999
+ describe() {
1000
+
1001
+ const desc = {
1002
+ rel: this.rel,
1003
+ peers: this.paths
1004
+ };
1005
+
1006
+ if (this.key !== null) {
1007
+ desc.key = this.key.key;
1008
+ }
1009
+
1010
+ if (this.peers[0].separator !== '.') {
1011
+ desc.options = { separator: this.peers[0].separator };
1012
+ }
1013
+
1014
+ return desc;
1015
+ }
1016
+ };
1017
+
1018
+
1019
+ internals.Keys = class extends Array {
1020
+
1021
+ concat(source) {
1022
+
1023
+ const result = this.slice();
1024
+
1025
+ const keys = new Map();
1026
+ for (let i = 0; i < result.length; ++i) {
1027
+ keys.set(result[i].key, i);
1028
+ }
1029
+
1030
+ for (const item of source) {
1031
+ const key = item.key;
1032
+ const pos = keys.get(key);
1033
+ if (pos !== undefined) {
1034
+ result[pos] = { key, schema: result[pos].schema.concat(item.schema) };
1035
+ }
1036
+ else {
1037
+ result.push(item);
1038
+ }
1039
+ }
1040
+
1041
+ return result;
1042
+ }
1043
+ };