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