@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,430 @@
1
+ 'use strict';
2
+
3
+ const { assert, merge } = require('@hapi/hoek');
4
+
5
+ const Any = require('./any');
6
+ const Common = require('../common');
7
+ const Compile = require('../compile');
8
+ const Errors = require('../errors');
9
+ const Ref = require('../ref');
10
+
11
+
12
+ const internals = {};
13
+
14
+
15
+ module.exports = Any.extend({
16
+
17
+ type: 'alternatives',
18
+
19
+ flags: {
20
+
21
+ match: { default: 'any' } // 'any', 'one', 'all'
22
+ },
23
+
24
+ terms: {
25
+
26
+ matches: { init: [], register: Ref.toSibling }
27
+ },
28
+
29
+ args(schema, ...schemas) {
30
+
31
+ if (schemas.length === 1) {
32
+ if (Array.isArray(schemas[0])) {
33
+ return schema.try(...schemas[0]);
34
+ }
35
+ }
36
+
37
+ return schema.try(...schemas);
38
+ },
39
+
40
+ validate(value, helpers) {
41
+
42
+ const { schema, error, state, prefs } = helpers;
43
+
44
+ // Match all or one
45
+
46
+ if (schema._flags.match) {
47
+ const matched = [];
48
+ const failed = [];
49
+
50
+ for (let i = 0; i < schema.$_terms.matches.length; ++i) {
51
+ const item = schema.$_terms.matches[i];
52
+ const localState = state.nest(item.schema, `match.${i}`);
53
+ localState.snapshot();
54
+
55
+ const result = item.schema.$_validate(value, localState, prefs);
56
+ if (!result.errors) {
57
+ matched.push(result.value);
58
+ localState.commit();
59
+ }
60
+ else {
61
+ failed.push(result.errors);
62
+ localState.restore();
63
+ }
64
+ }
65
+
66
+ if (matched.length === 0) {
67
+ const context = {
68
+ details: failed.map((f) => Errors.details(f, { override: false }))
69
+ };
70
+
71
+ return { errors: error('alternatives.any', context) };
72
+ }
73
+
74
+ // Match one
75
+
76
+ if (schema._flags.match === 'one') {
77
+ return matched.length === 1 ? { value: matched[0] } : { errors: error('alternatives.one') };
78
+ }
79
+
80
+ // Match all
81
+
82
+ if (matched.length !== schema.$_terms.matches.length) {
83
+ const context = {
84
+ details: failed.map((f) => Errors.details(f, { override: false }))
85
+ };
86
+
87
+ return { errors: error('alternatives.all', context) };
88
+ }
89
+
90
+ const isAnyObj = (alternative) => {
91
+
92
+ return alternative.$_terms.matches.some((v) => {
93
+
94
+ return v.schema.type === 'object' ||
95
+ (v.schema.type === 'alternatives' && isAnyObj(v.schema));
96
+ });
97
+ };
98
+
99
+ return isAnyObj(schema) ? { value: matched.reduce((acc, v) => merge(acc, v, { mergeArrays: false })) } : { value: matched[matched.length - 1] };
100
+ }
101
+
102
+ // Match any
103
+
104
+ const errors = [];
105
+ for (let i = 0; i < schema.$_terms.matches.length; ++i) {
106
+ const item = schema.$_terms.matches[i];
107
+
108
+ // Try
109
+
110
+ if (item.schema) {
111
+ const localState = state.nest(item.schema, `match.${i}`);
112
+ localState.snapshot();
113
+
114
+ const result = item.schema.$_validate(value, localState, prefs);
115
+ if (!result.errors) {
116
+ localState.commit();
117
+ return result;
118
+ }
119
+
120
+ localState.restore();
121
+ errors.push({ schema: item.schema, reports: result.errors });
122
+ continue;
123
+ }
124
+
125
+ // Conditional
126
+
127
+ const input = item.ref ? item.ref.resolve(value, state, prefs) : value;
128
+ const tests = item.is ? [item] : item.switch;
129
+
130
+ for (let j = 0; j < tests.length; ++j) {
131
+ const test = tests[j];
132
+ const { is, then, otherwise } = test;
133
+
134
+ const id = `match.${i}${item.switch ? '.' + j : ''}`;
135
+ if (!is.$_match(input, state.nest(is, `${id}.is`), prefs)) {
136
+ if (otherwise) {
137
+ return otherwise.$_validate(value, state.nest(otherwise, `${id}.otherwise`), prefs);
138
+ }
139
+ }
140
+ else if (then) {
141
+ return then.$_validate(value, state.nest(then, `${id}.then`), prefs);
142
+ }
143
+ }
144
+ }
145
+
146
+ return internals.errors(errors, helpers);
147
+ },
148
+
149
+ jsonSchema(schema, res, mode, options) {
150
+
151
+ const matches = [];
152
+
153
+ // Collect all alternative schemas from 'matches' term
154
+
155
+ for (const match of schema.$_terms.matches) {
156
+ if (match.schema) {
157
+ matches.push(match.schema.$_jsonSchema(mode, options));
158
+ }
159
+ else {
160
+ // Handle conditional matches (when/switch)
161
+
162
+ const tests = match.is ? [match] : match.switch;
163
+ for (const test of tests) {
164
+ if (test.then) {
165
+ matches.push(test.then.$_jsonSchema(mode, options));
166
+ }
167
+
168
+ if (test.otherwise) {
169
+ matches.push(test.otherwise.$_jsonSchema(mode, options));
170
+ }
171
+ }
172
+ }
173
+ }
174
+
175
+ if (matches.length) {
176
+ delete res.type;
177
+
178
+ // Map alternatives to 'anyOf' or 'oneOf' based on the match flag
179
+
180
+ const matchMode = schema._flags.match ?? 'any';
181
+ if (matchMode === 'one') {
182
+ res.oneOf = matches;
183
+ }
184
+ else {
185
+ res.anyOf = matches;
186
+ }
187
+ }
188
+
189
+ return res;
190
+ },
191
+
192
+ rules: {
193
+
194
+ conditional: {
195
+ method(condition, options) {
196
+
197
+ assert(!this._flags._endedSwitch, 'Unreachable condition');
198
+ assert(!this._flags.match, 'Cannot combine match mode', this._flags.match, 'with conditional rule');
199
+ assert(options.break === undefined, 'Cannot use break option with alternatives conditional');
200
+
201
+ const obj = this.clone();
202
+
203
+ const match = Compile.when(obj, condition, options);
204
+ const conditions = match.is ? [match] : match.switch;
205
+ for (const item of conditions) {
206
+ if (item.then &&
207
+ item.otherwise) {
208
+
209
+ obj.$_setFlag('_endedSwitch', true, { clone: false });
210
+ break;
211
+ }
212
+ }
213
+
214
+ obj.$_terms.matches.push(match);
215
+ return obj.$_mutateRebuild();
216
+ }
217
+ },
218
+
219
+ match: {
220
+ method(mode) {
221
+
222
+ assert(['any', 'one', 'all'].includes(mode), 'Invalid alternatives match mode', mode);
223
+
224
+ if (mode !== 'any') {
225
+ for (const match of this.$_terms.matches) {
226
+ assert(match.schema, 'Cannot combine match mode', mode, 'with conditional rules');
227
+ }
228
+ }
229
+
230
+ return this.$_setFlag('match', mode);
231
+ }
232
+ },
233
+
234
+ try: {
235
+ method(...schemas) {
236
+
237
+ assert(schemas.length, 'Missing alternative schemas');
238
+ Common.verifyFlat(schemas, 'try');
239
+
240
+ assert(!this._flags._endedSwitch, 'Unreachable condition');
241
+
242
+ const obj = this.clone();
243
+ for (const schema of schemas) {
244
+ obj.$_terms.matches.push({ schema: obj.$_compile(schema) });
245
+ }
246
+
247
+ return obj.$_mutateRebuild();
248
+ }
249
+ }
250
+ },
251
+
252
+ overrides: {
253
+
254
+ label(name) {
255
+
256
+ const obj = this.$_parent('label', name);
257
+ const each = (item, source) => {
258
+
259
+ return source.path[0] !== 'is' && typeof item._flags.label !== 'string' ? item.label(name) : undefined;
260
+ };
261
+
262
+ return obj.$_modify({ each, ref: false });
263
+ },
264
+
265
+ isAsync() {
266
+
267
+ if (this.$_terms.externals?.length) {
268
+ return true;
269
+ }
270
+
271
+ for (const match of this.$_terms.matches) {
272
+
273
+ if (match.schema?.isAsync()) {
274
+ return true;
275
+ }
276
+
277
+ if (match.then?.isAsync()) {
278
+ return true;
279
+ }
280
+
281
+ if (match.otherwise?.isAsync()) {
282
+ return true;
283
+ }
284
+ }
285
+
286
+ return false;
287
+ }
288
+ },
289
+
290
+ rebuild(schema) {
291
+
292
+ // Flag when an alternative type is an array
293
+
294
+ const each = (item) => {
295
+
296
+ if (Common.isSchema(item) &&
297
+ item.type === 'array') {
298
+
299
+ schema.$_setFlag('_arrayItems', true, { clone: false });
300
+ }
301
+ };
302
+
303
+ schema.$_modify({ each });
304
+ },
305
+
306
+ manifest: {
307
+
308
+ build(obj, desc) {
309
+
310
+ if (desc.matches) {
311
+ for (const match of desc.matches) {
312
+ const { schema, ref, is, not, then, otherwise } = match;
313
+ if (schema) {
314
+ obj = obj.try(schema);
315
+ }
316
+ else if (ref) {
317
+ obj = obj.conditional(ref, { is, then, not, otherwise, switch: match.switch });
318
+ }
319
+ else {
320
+ obj = obj.conditional(is, { then, otherwise });
321
+ }
322
+ }
323
+ }
324
+
325
+ return obj;
326
+ }
327
+ },
328
+
329
+ messages: {
330
+ 'alternatives.all': '{{#label}} does not match all of the required types',
331
+ 'alternatives.any': '{{#label}} does not match any of the allowed types',
332
+ 'alternatives.match': '{{#label}} does not match any of the allowed types',
333
+ 'alternatives.one': '{{#label}} matches more than one allowed type',
334
+ 'alternatives.types': '{{#label}} must be one of {{#types}}'
335
+ }
336
+ });
337
+
338
+
339
+ // Helpers
340
+
341
+ internals.errors = function (failures, { error, state }) {
342
+
343
+ // Nothing matched due to type criteria rules
344
+
345
+ if (!failures.length) {
346
+ return { errors: error('alternatives.any') };
347
+ }
348
+
349
+ // Single error
350
+
351
+ if (failures.length === 1) {
352
+ return { errors: failures[0].reports };
353
+ }
354
+
355
+ // Analyze reasons
356
+
357
+ const valids = new Set();
358
+ const complex = [];
359
+
360
+ for (const { reports, schema } of failures) {
361
+
362
+ // Multiple errors (!abortEarly)
363
+
364
+ if (reports.length > 1) {
365
+ return internals.unmatched(failures, error);
366
+ }
367
+
368
+ // Custom error
369
+
370
+ const report = reports[0];
371
+ if (report instanceof Errors.Report === false) {
372
+ return internals.unmatched(failures, error);
373
+ }
374
+
375
+ // Internal object or array error
376
+
377
+ if (report.state.path.length !== state.path.length) {
378
+ complex.push({ type: schema.type, report });
379
+ continue;
380
+ }
381
+
382
+ // Valids
383
+
384
+ if (report.code === 'any.only') {
385
+ for (const valid of report.local.valids) {
386
+ valids.add(valid);
387
+ }
388
+
389
+ continue;
390
+ }
391
+
392
+ // Base type
393
+
394
+ const [type, code] = report.code.split('.');
395
+ if (code !== 'base') {
396
+ complex.push({ type: schema.type, report });
397
+ }
398
+ else if (report.code === 'object.base') {
399
+ valids.add(report.local.type);
400
+ }
401
+ else {
402
+ valids.add(type);
403
+ }
404
+ }
405
+
406
+ // All errors are base types or valids
407
+
408
+ if (!complex.length) {
409
+ return { errors: error('alternatives.types', { types: [...valids] }) };
410
+ }
411
+
412
+ // Single complex error
413
+
414
+ if (complex.length === 1) {
415
+ return { errors: complex[0].report };
416
+ }
417
+
418
+ return internals.unmatched(failures, error);
419
+ };
420
+
421
+
422
+ internals.unmatched = function (failures, error) {
423
+
424
+ const errors = [];
425
+ for (const failure of failures) {
426
+ errors.push(...failure.reports);
427
+ }
428
+
429
+ return { errors: error('alternatives.match', Errors.details(errors, { override: false })) };
430
+ };
@@ -0,0 +1,174 @@
1
+ 'use strict';
2
+
3
+ const { assert } = require('@hapi/hoek');
4
+
5
+ const Base = require('../base');
6
+ const Common = require('../common');
7
+ const Messages = require('../messages');
8
+
9
+
10
+ const internals = {};
11
+
12
+
13
+ module.exports = Base.extend({
14
+
15
+ type: 'any',
16
+
17
+ flags: {
18
+
19
+ only: { default: false }
20
+ },
21
+
22
+ terms: {
23
+
24
+ alterations: { init: null },
25
+ examples: { init: null },
26
+ externals: { init: null },
27
+ metas: { init: [] },
28
+ notes: { init: [] },
29
+ shared: { init: null },
30
+ tags: { init: [] },
31
+ whens: { init: null }
32
+ },
33
+
34
+ rules: {
35
+
36
+ custom: {
37
+ method(method, description) {
38
+
39
+ assert(typeof method === 'function', 'Method must be a function');
40
+ assert(description === undefined || description && typeof description === 'string', 'Description must be a non-empty string');
41
+
42
+ return this.$_addRule({ name: 'custom', args: { method, description } });
43
+ },
44
+ validate(value, helpers, { method }) {
45
+
46
+ try {
47
+ return method(value, helpers);
48
+ }
49
+ catch (err) {
50
+ return helpers.error('any.custom', { error: err });
51
+ }
52
+ },
53
+ args: ['method', 'description'],
54
+ multi: true
55
+ },
56
+
57
+ messages: {
58
+ method(messages) {
59
+
60
+ return this.prefs({ messages });
61
+ }
62
+ },
63
+
64
+ shared: {
65
+ method(schema) {
66
+
67
+ assert(Common.isSchema(schema) && schema._flags.id, 'Schema must be a schema with an id');
68
+
69
+ const obj = this.clone();
70
+ obj.$_terms.shared = obj.$_terms.shared || [];
71
+ obj.$_terms.shared.push(schema);
72
+ obj.$_mutateRegister(schema);
73
+ return obj;
74
+ }
75
+ },
76
+
77
+ warning: {
78
+ method(code, local) {
79
+
80
+ assert(code && typeof code === 'string', 'Invalid warning code');
81
+
82
+ return this.$_addRule({ name: 'warning', args: { code, local }, warn: true });
83
+ },
84
+ validate(value, helpers, { code, local }) {
85
+
86
+ return helpers.error(code, local);
87
+ },
88
+ args: ['code', 'local'],
89
+ multi: true
90
+ }
91
+ },
92
+
93
+ modifiers: {
94
+
95
+ keep(rule, enabled = true) {
96
+
97
+ rule.keep = enabled;
98
+ },
99
+
100
+ message(rule, message) {
101
+
102
+ rule.message = Messages.compile(message);
103
+ },
104
+
105
+ warn(rule, enabled = true) {
106
+
107
+ rule.warn = enabled;
108
+ }
109
+ },
110
+
111
+ manifest: {
112
+
113
+ build(obj, desc) {
114
+
115
+ for (const key in desc) {
116
+ const values = desc[key];
117
+
118
+ if (['examples', 'externals', 'metas', 'notes', 'tags'].includes(key)) {
119
+ for (const value of values) {
120
+ obj = obj[key.slice(0, -1)](value);
121
+ }
122
+
123
+ continue;
124
+ }
125
+
126
+ if (key === 'alterations') {
127
+ const alter = {};
128
+ for (const { target, adjuster } of values) {
129
+ alter[target] = adjuster;
130
+ }
131
+
132
+ obj = obj.alter(alter);
133
+ continue;
134
+ }
135
+
136
+ if (key === 'whens') {
137
+ for (const value of values) {
138
+ const { ref, is, not, then, otherwise, concat } = value;
139
+ if (concat) {
140
+ obj = obj.concat(concat);
141
+ }
142
+ else if (ref) {
143
+ obj = obj.when(ref, { is, not, then, otherwise, switch: value.switch, break: value.break });
144
+ }
145
+ else {
146
+ obj = obj.when(is, { then, otherwise, break: value.break });
147
+ }
148
+ }
149
+
150
+ continue;
151
+ }
152
+
153
+ if (key === 'shared') {
154
+ for (const value of values) {
155
+ obj = obj.shared(value);
156
+ }
157
+ }
158
+ }
159
+
160
+ return obj;
161
+ }
162
+ },
163
+
164
+ messages: {
165
+ 'any.custom': '{{#label}} failed custom validation because {{#error.message}}',
166
+ 'any.default': '{{#label}} threw an error when running default method',
167
+ 'any.failover': '{{#label}} threw an error when running failover method',
168
+ 'any.invalid': '{{#label}} contains an invalid value',
169
+ 'any.only': '{{#label}} must be {if(#valids.length == 1, "", "one of ")}{{#valids}}',
170
+ 'any.ref': '{{#label}} {{#arg}} references {{:#ref}} which {{#reason}}',
171
+ 'any.required': '{{#label}} is required',
172
+ 'any.unknown': '{{#label}} is not allowed'
173
+ }
174
+ });