@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.
package/lib/base.js ADDED
@@ -0,0 +1,1317 @@
1
+ 'use strict';
2
+
3
+ const { assert, clone, deepEqual, merge } = require('@hapi/hoek');
4
+
5
+ const Cache = require('./cache');
6
+ const Common = require('./common');
7
+ const Compile = require('./compile');
8
+ const Errors = require('./errors');
9
+ const Extend = require('./extend');
10
+ const Manifest = require('./manifest');
11
+ const Messages = require('./messages');
12
+ const Modify = require('./modify');
13
+ const Ref = require('./ref');
14
+ const Trace = require('./trace');
15
+ const Validator = require('./validator');
16
+ const Values = require('./values');
17
+
18
+
19
+ const internals = {
20
+ standardTypes: new Set(['string', 'number', 'integer', 'boolean', 'object', 'array', 'null']),
21
+ jsonSchemaTarget: 'draft-2020-12',
22
+ primitiveTypes: new Set(['string', 'number', 'boolean']),
23
+ nullSchema: () => ({ type: 'null' })
24
+ };
25
+
26
+
27
+ internals.Base = class {
28
+
29
+ constructor(type) {
30
+
31
+ // Naming: public, _private, $_extension, $_mutate{action}
32
+
33
+ this.type = type;
34
+
35
+ this.$_root = null;
36
+ this._definition = {};
37
+ this._reset();
38
+ }
39
+
40
+ _reset() {
41
+
42
+ this._ids = new Modify.Ids();
43
+ this._preferences = null;
44
+ this._refs = new Ref.Manager();
45
+ this._cache = null;
46
+
47
+ this._valids = null;
48
+ this._invalids = null;
49
+
50
+ this._flags = {};
51
+ this._rules = [];
52
+ this._singleRules = new Map(); // The rule options passed for non-multi rules
53
+
54
+ this.$_terms = {}; // Hash of arrays of immutable objects (extended by other types)
55
+
56
+ this.$_temp = { // Runtime state (not cloned)
57
+ ruleset: null, // null: use last, false: error, number: start position
58
+ whens: {} // Runtime cache of generated whens
59
+ };
60
+ }
61
+
62
+ // Manifest
63
+
64
+ describe() {
65
+
66
+ assert(typeof Manifest.describe === 'function', 'Manifest functionality disabled');
67
+ return Manifest.describe(this);
68
+ }
69
+
70
+ $_jsonSchema(mode, options = {}) {
71
+
72
+ if (options.target !== undefined &&
73
+ options.target !== internals.jsonSchemaTarget) {
74
+
75
+ throw new Error(`Unsupported JSON Schema target: ${options.target}`);
76
+ }
77
+
78
+ const rootCall = !options.$defs;
79
+ const defs = options.$defs ?? {};
80
+
81
+ let schema = {};
82
+
83
+ const isTypeAny = this.type === 'any';
84
+ const isOnly = this._flags.only;
85
+
86
+ const valids = this._valids && Array.from(this._valids._values).filter((v) => v !== null);
87
+ let typesOverlap = true;
88
+
89
+ // If 'only' is set, check if the allowed values' types overlap with the schema type
90
+
91
+ if (valids && valids.length && isOnly && !isTypeAny) {
92
+ const types = new Set(valids.map((v) => typeof v));
93
+ typesOverlap = types.has(this.type) || (this.type === 'date' && types.has('object'));
94
+ }
95
+
96
+ // Set the JSON Schema 'type' if it's a standard type and there's an overlap
97
+
98
+ if (!isTypeAny && typesOverlap && internals.standardTypes.has(this.type)) {
99
+ schema.type = this.type;
100
+ }
101
+
102
+ if (this._flags.description) {
103
+ schema.description = this._flags.description;
104
+ }
105
+
106
+ if (this._flags.default !== undefined && typeof this._flags.default !== 'function') {
107
+ schema.default = this._flags.default;
108
+ }
109
+
110
+ // Apply type-specific JSON Schema conversion
111
+
112
+ const subOptions = { ...options, $defs: defs };
113
+ if (this._definition.jsonSchema && typesOverlap) {
114
+ schema = this._definition.jsonSchema(this, schema, mode, subOptions);
115
+ }
116
+
117
+ // Apply rule-specific JSON Schema conversions
118
+
119
+ for (const rule of this._rules) {
120
+ const definition = this._definition.rules[rule.name];
121
+ if (definition.jsonSchema && typesOverlap) {
122
+ schema = definition.jsonSchema(rule, schema, isOnly, mode, subOptions);
123
+ }
124
+ }
125
+
126
+ // Handle shared schemas
127
+
128
+ if (this.$_terms.shared) {
129
+ for (const shared of this.$_terms.shared) {
130
+ defs[shared._flags.id] = shared.$_jsonSchema(mode, subOptions);
131
+ }
132
+ }
133
+
134
+ if (rootCall && Object.keys(defs).length) {
135
+ schema.$defs = defs;
136
+ }
137
+
138
+ // Handle allowed values (valids)
139
+
140
+ if (this._valids) {
141
+
142
+ const values = valids.filter((v) => typeof v !== 'symbol');
143
+ if (values.length) {
144
+ if (this._flags.only) {
145
+ schema.enum = values;
146
+
147
+ const list = Common.intersect(new Set(values.map((v) => typeof v)), internals.primitiveTypes);
148
+
149
+ if (list.size) {
150
+ const types = [...list];
151
+ schema.type = types.length === 1 ? types[0] : types;
152
+ }
153
+ }
154
+ else {
155
+ // If values are allowed but not exclusive, add them via 'anyOf' if they differ from the main type
156
+
157
+ const otherTypes = values.filter((v) => typeof v !== this.type || isTypeAny);
158
+ if (otherTypes.length && !(isTypeAny && !isOnly)) {
159
+ if (!schema.anyOf) {
160
+ schema = {
161
+ anyOf: [schema]
162
+ };
163
+ }
164
+
165
+ schema.anyOf.push({ enum: otherTypes });
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ // Handle 'null' if it's an allowed value
172
+
173
+ if (this._valids && this._valids.has(null) && !(isTypeAny && !isOnly)) {
174
+ if (this._valids.length === 1 && (isTypeAny || isOnly)) {
175
+ schema.type = 'null';
176
+ }
177
+ else if (schema.type) {
178
+ schema.type = [schema.type, 'null'];
179
+ }
180
+ else if (schema.anyOf) {
181
+ schema.anyOf.unshift(internals.nullSchema());
182
+ }
183
+ else {
184
+ schema = {
185
+ anyOf: [
186
+ internals.nullSchema(),
187
+ schema
188
+ ]
189
+ };
190
+ }
191
+ }
192
+
193
+ // Handle conditionals (whens) by generating multiple possible schemas combined with 'anyOf'
194
+
195
+ if (this.$_terms.whens) {
196
+
197
+ const base = this.clone();
198
+ base.$_terms.whens = null;
199
+
200
+ const matches = [];
201
+ for (const when of this.$_terms.whens) {
202
+ const tests = when.is ? [when] : when.switch;
203
+ for (let i = 0; i < tests.length; ++i) {
204
+ const test = tests[i];
205
+ if (test.then) {
206
+ matches.push(base.concat(test.then).$_jsonSchema(mode, subOptions));
207
+ }
208
+
209
+ if (test.otherwise) {
210
+ matches.push(base.concat(test.otherwise).$_jsonSchema(mode, subOptions));
211
+ }
212
+
213
+ if (!test.then || (i === tests.length - 1 && !test.otherwise)) {
214
+ matches.push(base.$_jsonSchema(mode, subOptions));
215
+ }
216
+ }
217
+ }
218
+
219
+ const results = [];
220
+ for (const match of matches) {
221
+ if (!results.some((r) => deepEqual(r, match))) {
222
+ results.push(match);
223
+ }
224
+ }
225
+
226
+ return { anyOf: results };
227
+ }
228
+
229
+ return schema;
230
+ }
231
+
232
+ // Rules
233
+
234
+ allow(...values) {
235
+
236
+ Common.verifyFlat(values, 'allow');
237
+ return this._values(values, '_valids');
238
+ }
239
+
240
+ alter(targets) {
241
+
242
+ assert(targets && typeof targets === 'object' && !Array.isArray(targets), 'Invalid targets argument');
243
+ assert(!this._inRuleset(), 'Cannot set alterations inside a ruleset');
244
+
245
+ const obj = this.clone();
246
+ obj.$_terms.alterations = obj.$_terms.alterations || [];
247
+ for (const target in targets) {
248
+ const adjuster = targets[target];
249
+ assert(typeof adjuster === 'function', 'Alteration adjuster for', target, 'must be a function');
250
+ obj.$_terms.alterations.push({ target, adjuster });
251
+ }
252
+
253
+ obj.$_temp.ruleset = false;
254
+ return obj;
255
+ }
256
+
257
+ artifact(id) {
258
+
259
+ assert(id !== undefined, 'Artifact cannot be undefined');
260
+ assert(!this._cache, 'Cannot set an artifact with a rule cache');
261
+
262
+ return this.$_setFlag('artifact', id);
263
+ }
264
+
265
+ cast(to) {
266
+
267
+ assert(to === false || typeof to === 'string', 'Invalid to value');
268
+ assert(to === false || this._definition.cast[to], 'Type', this.type, 'does not support casting to', to);
269
+
270
+ return this.$_setFlag('cast', to === false ? undefined : to);
271
+ }
272
+
273
+ default(value, options) {
274
+
275
+ return this._default('default', value, options);
276
+ }
277
+
278
+ description(desc) {
279
+
280
+ assert(desc && typeof desc === 'string', 'Description must be a non-empty string');
281
+
282
+ return this.$_setFlag('description', desc);
283
+ }
284
+
285
+ empty(schema) {
286
+
287
+ const obj = this.clone();
288
+
289
+ if (schema !== undefined) {
290
+ schema = obj.$_compile(schema, { override: false });
291
+ }
292
+
293
+ return obj.$_setFlag('empty', schema, { clone: false });
294
+ }
295
+
296
+ error(err) {
297
+
298
+ assert(err, 'Missing error');
299
+ assert(err instanceof Error || typeof err === 'function', 'Must provide a valid Error object or a function');
300
+
301
+ return this.$_setFlag('error', err);
302
+ }
303
+
304
+ example(example, options = {}) {
305
+
306
+ assert(example !== undefined, 'Missing example');
307
+ Common.assertOptions(options, ['override']);
308
+
309
+ return this._inner('examples', example, { single: true, override: options.override });
310
+ }
311
+
312
+ external(method, description) {
313
+
314
+ if (typeof method === 'object') {
315
+ assert(!description, 'Cannot combine options with description');
316
+ description = method.description;
317
+ method = method.method;
318
+ }
319
+
320
+ assert(typeof method === 'function', 'Method must be a function');
321
+ assert(description === undefined || description && typeof description === 'string', 'Description must be a non-empty string');
322
+
323
+ return this._inner('externals', { method, description }, { single: true });
324
+ }
325
+
326
+ failover(value, options) {
327
+
328
+ return this._default('failover', value, options);
329
+ }
330
+
331
+ forbidden() {
332
+
333
+ return this.presence('forbidden');
334
+ }
335
+
336
+ id(id) {
337
+
338
+ if (!id) {
339
+ return this.$_setFlag('id', undefined);
340
+ }
341
+
342
+ assert(typeof id === 'string', 'id must be a non-empty string');
343
+ assert(/^[^\.]+$/.test(id), 'id cannot contain period character');
344
+
345
+ return this.$_setFlag('id', id);
346
+ }
347
+
348
+ invalid(...values) {
349
+
350
+ return this._values(values, '_invalids');
351
+ }
352
+
353
+ label(name) {
354
+
355
+ assert(name && typeof name === 'string', 'Label name must be a non-empty string');
356
+
357
+ return this.$_setFlag('label', name);
358
+ }
359
+
360
+ meta(meta) {
361
+
362
+ assert(meta !== undefined, 'Meta cannot be undefined');
363
+
364
+ return this._inner('metas', meta, { single: true });
365
+ }
366
+
367
+ note(...notes) {
368
+
369
+ assert(notes.length, 'Missing notes');
370
+ for (const note of notes) {
371
+ assert(note && typeof note === 'string', 'Notes must be non-empty strings');
372
+ }
373
+
374
+ return this._inner('notes', notes);
375
+ }
376
+
377
+ only(mode = true) {
378
+
379
+ assert(typeof mode === 'boolean', 'Invalid mode:', mode);
380
+
381
+ return this.$_setFlag('only', mode);
382
+ }
383
+
384
+ optional() {
385
+
386
+ return this.presence('optional');
387
+ }
388
+
389
+ prefs(prefs) {
390
+
391
+ assert(prefs, 'Missing preferences');
392
+ assert(prefs.context === undefined, 'Cannot override context');
393
+ assert(prefs.externals === undefined, 'Cannot override externals');
394
+ assert(prefs.warnings === undefined, 'Cannot override warnings');
395
+ assert(prefs.debug === undefined, 'Cannot override debug');
396
+
397
+ Common.checkPreferences(prefs);
398
+
399
+ const obj = this.clone();
400
+ obj._preferences = Common.preferences(obj._preferences, prefs);
401
+ return obj;
402
+ }
403
+
404
+ presence(mode) {
405
+
406
+ assert(['optional', 'required', 'forbidden'].includes(mode), 'Unknown presence mode', mode);
407
+
408
+ return this.$_setFlag('presence', mode);
409
+ }
410
+
411
+ raw(enabled = true) {
412
+
413
+ return this.$_setFlag('result', enabled ? 'raw' : undefined);
414
+ }
415
+
416
+ result(mode) {
417
+
418
+ assert(['raw', 'strip'].includes(mode), 'Unknown result mode', mode);
419
+
420
+ return this.$_setFlag('result', mode);
421
+ }
422
+
423
+ required() {
424
+
425
+ return this.presence('required');
426
+ }
427
+
428
+ strict(enabled) {
429
+
430
+ const obj = this.clone();
431
+
432
+ const convert = enabled === undefined ? false : !enabled;
433
+ obj._preferences = Common.preferences(obj._preferences, { convert });
434
+ return obj;
435
+ }
436
+
437
+ strip(enabled = true) {
438
+
439
+ return this.$_setFlag('result', enabled ? 'strip' : undefined);
440
+ }
441
+
442
+ tag(...tags) {
443
+
444
+ assert(tags.length, 'Missing tags');
445
+ for (const tag of tags) {
446
+ assert(tag && typeof tag === 'string', 'Tags must be non-empty strings');
447
+ }
448
+
449
+ return this._inner('tags', tags);
450
+ }
451
+
452
+ unit(name) {
453
+
454
+ assert(name && typeof name === 'string', 'Unit name must be a non-empty string');
455
+
456
+ return this.$_setFlag('unit', name);
457
+ }
458
+
459
+ valid(...values) {
460
+
461
+ Common.verifyFlat(values, 'valid');
462
+
463
+ const obj = this.allow(...values);
464
+ obj.$_setFlag('only', !!obj._valids, { clone: false });
465
+ return obj;
466
+ }
467
+
468
+ when(condition, options) {
469
+
470
+ const obj = this.clone();
471
+
472
+ if (!obj.$_terms.whens) {
473
+ obj.$_terms.whens = [];
474
+ }
475
+
476
+ const when = Compile.when(obj, condition, options);
477
+ if (!['any', 'link'].includes(obj.type)) {
478
+ const conditions = when.is ? [when] : when.switch;
479
+ for (const item of conditions) {
480
+ assert(!item.then || item.then.type === 'any' || item.then.type === obj.type, 'Cannot combine', obj.type, 'with', item.then && item.then.type);
481
+ assert(!item.otherwise || item.otherwise.type === 'any' || item.otherwise.type === obj.type, 'Cannot combine', obj.type, 'with', item.otherwise && item.otherwise.type);
482
+
483
+ }
484
+ }
485
+
486
+ obj.$_terms.whens.push(when);
487
+ return obj.$_mutateRebuild();
488
+ }
489
+
490
+ // Helpers
491
+
492
+ cache(cache) {
493
+
494
+ assert(!this._inRuleset(), 'Cannot set caching inside a ruleset');
495
+ assert(!this._cache, 'Cannot override schema cache');
496
+ assert(this._flags.artifact === undefined, 'Cannot cache a rule with an artifact');
497
+
498
+ const obj = this.clone();
499
+ obj._cache = cache || Cache.provider.provision();
500
+ obj.$_temp.ruleset = false;
501
+ return obj;
502
+ }
503
+
504
+ clone() {
505
+
506
+ const obj = Object.create(Object.getPrototypeOf(this));
507
+ return this._assign(obj);
508
+ }
509
+
510
+ concat(source) {
511
+
512
+ assert(Common.isSchema(source), 'Invalid schema object');
513
+ assert(this.type === 'any' || source.type === 'any' || source.type === this.type, 'Cannot merge type', this.type, 'with another type:', source.type);
514
+ assert(!this._inRuleset(), 'Cannot concatenate onto a schema with open ruleset');
515
+ assert(!source._inRuleset(), 'Cannot concatenate a schema with open ruleset');
516
+
517
+ let obj = this.clone();
518
+
519
+ if (this.type === 'any' &&
520
+ source.type !== 'any') {
521
+
522
+ // Change obj to match source type
523
+
524
+ const tmpObj = source.clone();
525
+ for (const key of Object.keys(obj)) {
526
+ if (key !== 'type') {
527
+ tmpObj[key] = obj[key];
528
+ }
529
+ }
530
+
531
+ obj = tmpObj;
532
+ }
533
+
534
+ obj._ids.concat(source._ids);
535
+ obj._refs.register(source, Ref.toSibling);
536
+
537
+ obj._preferences = obj._preferences ? Common.preferences(obj._preferences, source._preferences) : source._preferences;
538
+ obj._valids = Values.merge(obj._valids, source._valids, source._invalids);
539
+ obj._invalids = Values.merge(obj._invalids, source._invalids, source._valids);
540
+
541
+ // Remove unique rules present in source
542
+
543
+ for (const name of source._singleRules.keys()) {
544
+ if (obj._singleRules.has(name)) {
545
+ obj._rules = obj._rules.filter((target) => target.keep || target.name !== name);
546
+ obj._singleRules.delete(name);
547
+ }
548
+ }
549
+
550
+ // Rules
551
+
552
+ for (const test of source._rules) {
553
+ if (!source._definition.rules[test.method].multi) {
554
+ obj._singleRules.set(test.name, test);
555
+ }
556
+
557
+ obj._rules.push(test);
558
+ }
559
+
560
+ // Flags
561
+
562
+ if (obj._flags.empty &&
563
+ source._flags.empty) {
564
+
565
+ obj._flags.empty = obj._flags.empty.concat(source._flags.empty);
566
+ const flags = Object.assign({}, source._flags);
567
+ delete flags.empty;
568
+ merge(obj._flags, flags);
569
+ }
570
+ else if (source._flags.empty) {
571
+ obj._flags.empty = source._flags.empty;
572
+ const flags = Object.assign({}, source._flags);
573
+ delete flags.empty;
574
+ merge(obj._flags, flags);
575
+ }
576
+ else {
577
+ merge(obj._flags, source._flags);
578
+ }
579
+
580
+ // Terms
581
+
582
+ for (const key in source.$_terms) {
583
+ const terms = source.$_terms[key];
584
+ if (!terms) {
585
+ if (!obj.$_terms[key]) {
586
+ obj.$_terms[key] = terms;
587
+ }
588
+
589
+ continue;
590
+ }
591
+
592
+ if (!obj.$_terms[key]) {
593
+ obj.$_terms[key] = terms.slice();
594
+ continue;
595
+ }
596
+
597
+ obj.$_terms[key] = obj.$_terms[key].concat(terms);
598
+ }
599
+
600
+ // Tracing
601
+
602
+ if (this.$_root._tracer) {
603
+ this.$_root._tracer._combine(obj, [this, source]);
604
+ }
605
+
606
+ // Rebuild
607
+
608
+ return obj.$_mutateRebuild();
609
+ }
610
+
611
+ extend(options) {
612
+
613
+ assert(!options.base, 'Cannot extend type with another base');
614
+
615
+ return Extend.type(this, options);
616
+ }
617
+
618
+ extract(path) {
619
+
620
+ path = Array.isArray(path) ? path : path.split('.');
621
+ return this._ids.reach(path);
622
+ }
623
+
624
+ fork(paths, adjuster) {
625
+
626
+ assert(!this._inRuleset(), 'Cannot fork inside a ruleset');
627
+
628
+ let obj = this; // eslint-disable-line consistent-this
629
+ for (let path of [].concat(paths)) {
630
+ path = Array.isArray(path) ? path : path.split('.');
631
+ obj = obj._ids.fork(path, adjuster, obj);
632
+ }
633
+
634
+ obj.$_temp.ruleset = false;
635
+ return obj;
636
+ }
637
+
638
+ isAsync() {
639
+
640
+ if (Boolean(this.$_terms.externals?.length)) {
641
+ return true;
642
+ }
643
+
644
+ if (this.$_terms.whens) {
645
+ for (const when of this.$_terms.whens) {
646
+ if (when.then?.isAsync()) {
647
+ return true;
648
+ }
649
+
650
+ if (when.otherwise?.isAsync()) {
651
+ return true;
652
+ }
653
+
654
+ if (when.switch) {
655
+ for (const item of when.switch) {
656
+ if (item.then?.isAsync()) {
657
+ return true;
658
+ }
659
+
660
+ if (item.otherwise?.isAsync()) {
661
+ return true;
662
+ }
663
+ }
664
+ }
665
+ }
666
+ }
667
+
668
+ return false;
669
+ }
670
+
671
+ rule(options) {
672
+
673
+ const def = this._definition;
674
+ Common.assertOptions(options, Object.keys(def.modifiers));
675
+
676
+ assert(this.$_temp.ruleset !== false, 'Cannot apply rules to empty ruleset or the last rule added does not support rule properties');
677
+ const start = this.$_temp.ruleset === null ? this._rules.length - 1 : this.$_temp.ruleset;
678
+ assert(start >= 0 && start < this._rules.length, 'Cannot apply rules to empty ruleset');
679
+
680
+ const obj = this.clone();
681
+
682
+ for (let i = start; i < obj._rules.length; ++i) {
683
+ const original = obj._rules[i];
684
+ const rule = clone(original);
685
+
686
+ for (const name in options) {
687
+ def.modifiers[name](rule, options[name]);
688
+ assert(rule.name === original.name, 'Cannot change rule name');
689
+ }
690
+
691
+ obj._rules[i] = rule;
692
+
693
+ if (obj._singleRules.get(rule.name) === original) {
694
+ obj._singleRules.set(rule.name, rule);
695
+ }
696
+ }
697
+
698
+ obj.$_temp.ruleset = false;
699
+ return obj.$_mutateRebuild();
700
+ }
701
+
702
+ get ruleset() {
703
+
704
+ assert(!this._inRuleset(), 'Cannot start a new ruleset without closing the previous one');
705
+
706
+ const obj = this.clone();
707
+ obj.$_temp.ruleset = obj._rules.length;
708
+ return obj;
709
+ }
710
+
711
+ get $() {
712
+
713
+ return this.ruleset;
714
+ }
715
+
716
+ tailor(targets) {
717
+
718
+ targets = [].concat(targets);
719
+
720
+ assert(!this._inRuleset(), 'Cannot tailor inside a ruleset');
721
+
722
+ let obj = this; // eslint-disable-line consistent-this
723
+
724
+ if (this.$_terms.alterations) {
725
+ for (const { target, adjuster } of this.$_terms.alterations) {
726
+ if (targets.includes(target)) {
727
+ obj = adjuster(obj);
728
+ assert(Common.isSchema(obj), 'Alteration adjuster for', target, 'failed to return a schema object');
729
+ }
730
+ }
731
+ }
732
+
733
+ obj = obj.$_modify({ each: (item) => item.tailor(targets), ref: false });
734
+ obj.$_temp.ruleset = false;
735
+ return obj.$_mutateRebuild();
736
+ }
737
+
738
+ tracer() {
739
+
740
+ return Trace.location ? Trace.location(this) : this; // $lab:coverage:ignore$
741
+ }
742
+
743
+ validate(value, options) {
744
+
745
+ return Validator.entry(value, this, options);
746
+ }
747
+
748
+ validateAsync(value, options) {
749
+
750
+ return Validator.entryAsync(value, this, options);
751
+ }
752
+
753
+ // Extensions
754
+
755
+ $_addRule(options) {
756
+
757
+ // Normalize rule
758
+
759
+ if (typeof options === 'string') {
760
+ options = { name: options };
761
+ }
762
+
763
+ assert(options && typeof options === 'object', 'Invalid options');
764
+ assert(options.name && typeof options.name === 'string', 'Invalid rule name');
765
+
766
+ for (const key in options) {
767
+ assert(key[0] !== '_', 'Cannot set private rule properties');
768
+ }
769
+
770
+ const rule = Object.assign({}, options); // Shallow cloned
771
+ rule._resolve = [];
772
+ rule.method = rule.method || rule.name;
773
+
774
+ const definition = this._definition.rules[rule.method];
775
+ const args = rule.args;
776
+
777
+ assert(definition, 'Unknown rule', rule.method);
778
+
779
+ // Args
780
+
781
+ const obj = this.clone();
782
+
783
+ if (args) {
784
+ assert(Object.keys(args).length === 1 || Object.keys(args).length === this._definition.rules[rule.name].args.length, 'Invalid rule definition for', this.type, rule.name);
785
+
786
+ for (const key in args) {
787
+ let arg = args[key];
788
+
789
+ if (definition.argsByName) {
790
+ const resolver = definition.argsByName.get(key);
791
+
792
+ if (resolver.ref &&
793
+ Common.isResolvable(arg)) {
794
+
795
+ rule._resolve.push(key);
796
+ obj.$_mutateRegister(arg);
797
+ }
798
+ else {
799
+ if (resolver.normalize) {
800
+ arg = resolver.normalize(arg);
801
+ args[key] = arg;
802
+ }
803
+
804
+ if (resolver.assert) {
805
+ const error = Common.validateArg(arg, key, resolver);
806
+ assert(!error, error, 'or reference');
807
+ }
808
+ }
809
+ }
810
+
811
+ if (arg === undefined) {
812
+ delete args[key];
813
+ continue;
814
+ }
815
+
816
+ args[key] = arg;
817
+ }
818
+ }
819
+
820
+ // Unique rules
821
+
822
+ if (!definition.multi) {
823
+ obj._ruleRemove(rule.name, { clone: false });
824
+ obj._singleRules.set(rule.name, rule);
825
+ }
826
+
827
+ if (obj.$_temp.ruleset === false) {
828
+ obj.$_temp.ruleset = null;
829
+ }
830
+
831
+ if (definition.priority) {
832
+ obj._rules.unshift(rule);
833
+ }
834
+ else {
835
+ obj._rules.push(rule);
836
+ }
837
+
838
+ return obj;
839
+ }
840
+
841
+ $_compile(schema, options) {
842
+
843
+ return Compile.schema(this.$_root, schema, options);
844
+ }
845
+
846
+ $_createError(code, value, local, state, prefs, options = {}) {
847
+
848
+ const flags = options.flags !== false ? this._flags : {};
849
+ const messages = options.messages ? Messages.merge(this._definition.messages, options.messages) : this._definition.messages;
850
+ return new Errors.Report(code, value, local, flags, messages, state, prefs);
851
+ }
852
+
853
+ $_getFlag(name) {
854
+
855
+ return this._flags[name];
856
+ }
857
+
858
+ $_getRule(name) {
859
+
860
+ return this._singleRules.get(name);
861
+ }
862
+
863
+ $_mapLabels(path) {
864
+
865
+ path = Array.isArray(path) ? path : path.split('.');
866
+ return this._ids.labels(path);
867
+ }
868
+
869
+ $_match(value, state, prefs, overrides) {
870
+
871
+ prefs = Object.assign({}, prefs); // Shallow cloned
872
+ prefs.abortEarly = true;
873
+ prefs._externals = false;
874
+
875
+ state.snapshot();
876
+ const result = !Validator.validate(value, this, state, prefs, overrides).errors;
877
+ state.restore();
878
+
879
+ return result;
880
+ }
881
+
882
+ $_modify(options) {
883
+
884
+ Common.assertOptions(options, ['each', 'once', 'ref', 'schema']);
885
+ return Modify.schema(this, options) || this;
886
+ }
887
+
888
+ $_mutateRebuild() {
889
+
890
+ assert(!this._inRuleset(), 'Cannot add this rule inside a ruleset');
891
+
892
+ this._refs.reset();
893
+ this._ids.reset();
894
+
895
+ const each = (item, { source, name, path, key }) => {
896
+
897
+ const family = this._definition[source][name] && this._definition[source][name].register;
898
+ if (family !== false) {
899
+ this.$_mutateRegister(item, { family, key });
900
+ }
901
+ };
902
+
903
+ this.$_modify({ each });
904
+
905
+ if (this._definition.rebuild) {
906
+ this._definition.rebuild(this);
907
+ }
908
+
909
+ this.$_temp.ruleset = false;
910
+ return this;
911
+ }
912
+
913
+ $_mutateRegister(schema, { family, key } = {}) {
914
+
915
+ this._refs.register(schema, family);
916
+ this._ids.register(schema, { key });
917
+ }
918
+
919
+ $_property(name) {
920
+
921
+ return this._definition.properties[name];
922
+ }
923
+
924
+ $_reach(path) {
925
+
926
+ return this._ids.reach(path);
927
+ }
928
+
929
+ $_rootReferences() {
930
+
931
+ return this._refs.roots();
932
+ }
933
+
934
+ $_setFlag(name, value, options = {}) {
935
+
936
+ assert(name[0] === '_' || !this._inRuleset(), 'Cannot set flag inside a ruleset');
937
+
938
+ const flag = this._definition.flags[name] || {};
939
+ if (deepEqual(value, flag.default)) {
940
+ value = undefined;
941
+ }
942
+
943
+ if (deepEqual(value, this._flags[name])) {
944
+ return this;
945
+ }
946
+
947
+ const obj = options.clone !== false ? this.clone() : this;
948
+
949
+ if (value !== undefined) {
950
+ obj._flags[name] = value;
951
+ obj.$_mutateRegister(value);
952
+ }
953
+ else {
954
+ delete obj._flags[name];
955
+ }
956
+
957
+ if (name[0] !== '_') {
958
+ obj.$_temp.ruleset = false;
959
+ }
960
+
961
+ return obj;
962
+ }
963
+
964
+ $_parent(method, ...args) {
965
+
966
+ return this[method][Common.symbols.parent].call(this, ...args);
967
+ }
968
+
969
+ $_validate(value, state, prefs) {
970
+
971
+ return Validator.validate(value, this, state, prefs);
972
+ }
973
+
974
+ // Internals
975
+
976
+ _assign(target) {
977
+
978
+ target.type = this.type;
979
+
980
+ target.$_root = this.$_root;
981
+
982
+ target.$_temp = Object.assign({}, this.$_temp);
983
+ target.$_temp.whens = {};
984
+
985
+ target._ids = this._ids.clone();
986
+ target._preferences = this._preferences;
987
+ target._valids = this._valids && this._valids.clone();
988
+ target._invalids = this._invalids && this._invalids.clone();
989
+ target._rules = this._rules.slice();
990
+ target._singleRules = clone(this._singleRules, { shallow: true });
991
+ target._refs = this._refs.clone();
992
+ target._flags = Object.assign({}, this._flags);
993
+ target._cache = null;
994
+
995
+ target.$_terms = {};
996
+ for (const key in this.$_terms) {
997
+ target.$_terms[key] = this.$_terms[key] ? this.$_terms[key].slice() : null;
998
+ }
999
+
1000
+ // Backwards compatibility
1001
+
1002
+ target.$_super = {};
1003
+ for (const override in this.$_super) {
1004
+ target.$_super[override] = this._super[override].bind(target);
1005
+ }
1006
+
1007
+ return target;
1008
+ }
1009
+
1010
+ _bare() {
1011
+
1012
+ const obj = this.clone();
1013
+ obj._reset();
1014
+
1015
+ const terms = obj._definition.terms;
1016
+ for (const name in terms) {
1017
+ const term = terms[name];
1018
+ obj.$_terms[name] = term.init;
1019
+ }
1020
+
1021
+ return obj.$_mutateRebuild();
1022
+ }
1023
+
1024
+ _default(flag, value, options = {}) {
1025
+
1026
+ Common.assertOptions(options, 'literal');
1027
+
1028
+ assert(value !== undefined, 'Missing', flag, 'value');
1029
+ assert(typeof value === 'function' || !options.literal, 'Only function value supports literal option');
1030
+
1031
+ if (typeof value === 'function' &&
1032
+ options.literal) {
1033
+
1034
+ value = {
1035
+ [Common.symbols.literal]: true,
1036
+ literal: value
1037
+ };
1038
+ }
1039
+
1040
+ const obj = this.$_setFlag(flag, value);
1041
+ return obj;
1042
+ }
1043
+
1044
+ _generate(value, state, prefs) {
1045
+
1046
+ if (!this.$_terms.whens) {
1047
+ return { schema: this };
1048
+ }
1049
+
1050
+ // Collect matching whens
1051
+
1052
+ const whens = [];
1053
+ const ids = [];
1054
+ for (let i = 0; i < this.$_terms.whens.length; ++i) {
1055
+ const when = this.$_terms.whens[i];
1056
+
1057
+ if (when.concat) {
1058
+ whens.push(when.concat);
1059
+ ids.push(`${i}.concat`);
1060
+ continue;
1061
+ }
1062
+
1063
+ const input = when.ref ? when.ref.resolve(value, state, prefs) : value;
1064
+ const tests = when.is ? [when] : when.switch;
1065
+ const before = ids.length;
1066
+
1067
+ for (let j = 0; j < tests.length; ++j) {
1068
+ const { is, then, otherwise } = tests[j];
1069
+
1070
+ const baseId = `${i}${when.switch ? '.' + j : ''}`;
1071
+ if (is.$_match(input, state.nest(is, `${baseId}.is`), prefs)) {
1072
+ if (then) {
1073
+ const localState = state.localize([...state.path, `${baseId}.then`], state.ancestors, state.schemas);
1074
+ const { schema: generated, id } = then._generate(value, localState, prefs);
1075
+ whens.push(generated);
1076
+ ids.push(`${baseId}.then${id ? `(${id})` : ''}`);
1077
+ break;
1078
+ }
1079
+ }
1080
+ else if (otherwise) {
1081
+ const localState = state.localize([...state.path, `${baseId}.otherwise`], state.ancestors, state.schemas);
1082
+ const { schema: generated, id } = otherwise._generate(value, localState, prefs);
1083
+ whens.push(generated);
1084
+ ids.push(`${baseId}.otherwise${id ? `(${id})` : ''}`);
1085
+ break;
1086
+ }
1087
+ }
1088
+
1089
+ if (when.break &&
1090
+ ids.length > before) { // Something matched
1091
+
1092
+ break;
1093
+ }
1094
+ }
1095
+
1096
+ // Check cache
1097
+
1098
+ const id = ids.join(', ');
1099
+ state.mainstay.tracer.debug(state, 'rule', 'when', id);
1100
+
1101
+ if (!id) {
1102
+ return { schema: this };
1103
+ }
1104
+
1105
+ if (!state.mainstay.tracer.active &&
1106
+ this.$_temp.whens[id]) {
1107
+
1108
+ return { schema: this.$_temp.whens[id], id };
1109
+ }
1110
+
1111
+ // Generate dynamic schema
1112
+
1113
+ let obj = this; // eslint-disable-line consistent-this
1114
+ if (this._definition.generate) {
1115
+ obj = this._definition.generate(this, value, state, prefs);
1116
+ }
1117
+
1118
+ // Apply whens
1119
+
1120
+ for (const when of whens) {
1121
+ obj = obj.concat(when);
1122
+ }
1123
+
1124
+ // Tracing
1125
+
1126
+ if (this.$_root._tracer) {
1127
+ this.$_root._tracer._combine(obj, [this, ...whens]);
1128
+ }
1129
+
1130
+ // Cache result
1131
+
1132
+ this.$_temp.whens[id] = obj;
1133
+ return { schema: obj, id };
1134
+ }
1135
+
1136
+ _inner(type, values, options = {}) {
1137
+
1138
+ assert(!this._inRuleset(), `Cannot set ${type} inside a ruleset`);
1139
+
1140
+ const obj = this.clone();
1141
+ if (!obj.$_terms[type] ||
1142
+ options.override) {
1143
+
1144
+ obj.$_terms[type] = [];
1145
+ }
1146
+
1147
+ if (options.single) {
1148
+ obj.$_terms[type].push(values);
1149
+ }
1150
+ else {
1151
+ obj.$_terms[type].push(...values);
1152
+ }
1153
+
1154
+ obj.$_temp.ruleset = false;
1155
+ return obj;
1156
+ }
1157
+
1158
+ _inRuleset() {
1159
+
1160
+ return this.$_temp.ruleset !== null && this.$_temp.ruleset !== false;
1161
+ }
1162
+
1163
+ _ruleRemove(name, options = {}) {
1164
+
1165
+ if (!this._singleRules.has(name)) {
1166
+ return this;
1167
+ }
1168
+
1169
+ const obj = options.clone !== false ? this.clone() : this;
1170
+
1171
+ obj._singleRules.delete(name);
1172
+
1173
+ const filtered = [];
1174
+ for (let i = 0; i < obj._rules.length; ++i) {
1175
+ const test = obj._rules[i];
1176
+ if (test.name === name &&
1177
+ !test.keep) {
1178
+
1179
+ if (obj._inRuleset() &&
1180
+ i < obj.$_temp.ruleset) {
1181
+
1182
+ --obj.$_temp.ruleset;
1183
+ }
1184
+
1185
+ continue;
1186
+ }
1187
+
1188
+ filtered.push(test);
1189
+ }
1190
+
1191
+ obj._rules = filtered;
1192
+ return obj;
1193
+ }
1194
+
1195
+ _values(values, key) {
1196
+
1197
+ Common.verifyFlat(values, key.slice(1, -1));
1198
+
1199
+ const obj = this.clone();
1200
+
1201
+ const override = values[0] === Common.symbols.override;
1202
+ if (override) {
1203
+ values = values.slice(1);
1204
+ }
1205
+
1206
+ if (!obj[key] &&
1207
+ values.length) {
1208
+
1209
+ obj[key] = new Values();
1210
+ }
1211
+ else if (override) {
1212
+ obj[key] = values.length ? new Values() : null;
1213
+ obj.$_mutateRebuild();
1214
+ }
1215
+
1216
+ if (!obj[key]) {
1217
+ return obj;
1218
+ }
1219
+
1220
+ if (override) {
1221
+ obj[key].override();
1222
+ }
1223
+
1224
+ for (const value of values) {
1225
+ assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');
1226
+ assert(value !== Common.symbols.override, 'Override must be the first value');
1227
+
1228
+ const other = key === '_invalids' ? '_valids' : '_invalids';
1229
+ if (obj[other]) {
1230
+ obj[other].remove(value);
1231
+ if (!obj[other].length) {
1232
+ assert(key === '_valids' || !obj._flags.only, 'Setting invalid value', value, 'leaves schema rejecting all values due to previous valid rule');
1233
+ obj[other] = null;
1234
+ }
1235
+ }
1236
+
1237
+ obj[key].add(value, obj._refs);
1238
+ }
1239
+
1240
+ return obj;
1241
+ }
1242
+
1243
+ // Standard Schema
1244
+
1245
+ get '~standard'() {
1246
+
1247
+ const mapToStandardError = (error) => {
1248
+
1249
+ let issues;
1250
+ if (Errors.ValidationError.isError(error)) {
1251
+ issues = error.details.map(({ message, path }) => ({
1252
+ message,
1253
+ path
1254
+ }));
1255
+ }
1256
+ else {
1257
+ issues = [{
1258
+ message: error.message
1259
+ }];
1260
+ }
1261
+
1262
+ return {
1263
+ issues
1264
+ };
1265
+ };
1266
+
1267
+ const mapToStandardValue = (value) => ({ value });
1268
+
1269
+ return {
1270
+ version: 1,
1271
+ vendor: 'joi',
1272
+ validate: (value, options) => {
1273
+
1274
+ const result = Validator.standard(value, this, options);
1275
+
1276
+ if (result instanceof Promise) {
1277
+ return result
1278
+ .then(mapToStandardValue, mapToStandardError);
1279
+ }
1280
+
1281
+ if (!result.error) {
1282
+ return mapToStandardValue(result.value);
1283
+ }
1284
+
1285
+ return mapToStandardError(result.error);
1286
+ },
1287
+ jsonSchema: {
1288
+ input: (options) => this.$_jsonSchema('input', options),
1289
+ output: (options) => this.$_jsonSchema('output', options)
1290
+ }
1291
+ };
1292
+ }
1293
+ };
1294
+
1295
+
1296
+ internals.Base.prototype[Common.symbols.any] = {
1297
+ version: Common.version,
1298
+ compile: Compile.compile,
1299
+ root: '$_root'
1300
+ };
1301
+
1302
+
1303
+ internals.Base.prototype.isImmutable = true; // Prevents Hoek from deep cloning schema objects (must be on prototype)
1304
+
1305
+
1306
+ // Aliases
1307
+
1308
+ internals.Base.prototype.deny = internals.Base.prototype.invalid;
1309
+ internals.Base.prototype.disallow = internals.Base.prototype.invalid;
1310
+ internals.Base.prototype.equal = internals.Base.prototype.valid;
1311
+ internals.Base.prototype.exist = internals.Base.prototype.required;
1312
+ internals.Base.prototype.not = internals.Base.prototype.invalid;
1313
+ internals.Base.prototype.options = internals.Base.prototype.prefs;
1314
+ internals.Base.prototype.preferences = internals.Base.prototype.prefs;
1315
+
1316
+
1317
+ module.exports = new internals.Base();