@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,475 @@
1
+ 'use strict';
2
+
3
+ const { assert, clone } = require('@hapi/hoek');
4
+
5
+ const Common = require('./common');
6
+ const Messages = require('./messages');
7
+ const Ref = require('./ref');
8
+ const Template = require('./template');
9
+
10
+ let Schemas;
11
+
12
+
13
+ const internals = {};
14
+
15
+
16
+ exports.describe = function (schema) {
17
+
18
+ const def = schema._definition;
19
+
20
+ // Type
21
+
22
+ const desc = {
23
+ type: schema.type,
24
+ flags: {},
25
+ rules: []
26
+ };
27
+
28
+ // Flags
29
+
30
+ for (const flag in schema._flags) {
31
+ if (flag[0] !== '_') {
32
+ desc.flags[flag] = internals.describe(schema._flags[flag]);
33
+ }
34
+ }
35
+
36
+ if (!Object.keys(desc.flags).length) {
37
+ delete desc.flags;
38
+ }
39
+
40
+ // Preferences
41
+
42
+ if (schema._preferences) {
43
+ desc.preferences = clone(schema._preferences, { shallow: ['messages'] });
44
+ delete desc.preferences[Common.symbols.prefs];
45
+ if (desc.preferences.messages) {
46
+ desc.preferences.messages = Messages.decompile(desc.preferences.messages);
47
+ }
48
+ }
49
+
50
+ // Allow / Invalid
51
+
52
+ if (schema._valids) {
53
+ desc.allow = schema._valids.describe();
54
+ }
55
+
56
+ if (schema._invalids) {
57
+ desc.invalid = schema._invalids.describe();
58
+ }
59
+
60
+ // Rules
61
+
62
+ for (const rule of schema._rules) {
63
+ const ruleDef = def.rules[rule.name];
64
+ if (ruleDef.manifest === false) { // Defaults to true
65
+ continue;
66
+ }
67
+
68
+ const item = { name: rule.name };
69
+
70
+ for (const custom in def.modifiers) {
71
+ if (rule[custom] !== undefined) {
72
+ item[custom] = internals.describe(rule[custom]);
73
+ }
74
+ }
75
+
76
+ if (rule.args) {
77
+ item.args = {};
78
+ for (const key in rule.args) {
79
+ const arg = rule.args[key];
80
+ if (key === 'options' &&
81
+ !Object.keys(arg).length) {
82
+
83
+ continue;
84
+ }
85
+
86
+ item.args[key] = internals.describe(arg, { assign: key });
87
+ }
88
+
89
+ if (!Object.keys(item.args).length) {
90
+ delete item.args;
91
+ }
92
+ }
93
+
94
+ desc.rules.push(item);
95
+ }
96
+
97
+ if (!desc.rules.length) {
98
+ delete desc.rules;
99
+ }
100
+
101
+ // Terms (must be last to verify no name conflicts)
102
+
103
+ for (const term in schema.$_terms) {
104
+ if (term[0] === '_') {
105
+ continue;
106
+ }
107
+
108
+ assert(!desc[term], 'Cannot describe schema due to internal name conflict with', term);
109
+
110
+ const items = schema.$_terms[term];
111
+ if (!items) {
112
+ continue;
113
+ }
114
+
115
+ if (items instanceof Map) {
116
+ if (items.size) {
117
+ desc[term] = [...items.entries()];
118
+ }
119
+
120
+ continue;
121
+ }
122
+
123
+ if (Common.isValues(items)) {
124
+ desc[term] = items.describe();
125
+ continue;
126
+ }
127
+
128
+ assert(def.terms[term], 'Term', term, 'missing configuration');
129
+ const manifest = def.terms[term].manifest;
130
+ const mapped = typeof manifest === 'object';
131
+ if (!items.length &&
132
+ !mapped) {
133
+
134
+ continue;
135
+ }
136
+
137
+ const normalized = [];
138
+ for (const item of items) {
139
+ normalized.push(internals.describe(item));
140
+ }
141
+
142
+ // Mapped
143
+
144
+ if (mapped) {
145
+ const { from, to } = manifest.mapped;
146
+ desc[term] = {};
147
+ for (const item of normalized) {
148
+ desc[term][item[to]] = item[from];
149
+ }
150
+
151
+ continue;
152
+ }
153
+
154
+ // Single
155
+
156
+ if (manifest === 'single') {
157
+ assert(normalized.length === 1, 'Term', term, 'contains more than one item');
158
+ desc[term] = normalized[0];
159
+ continue;
160
+ }
161
+
162
+ // Array
163
+
164
+ desc[term] = normalized;
165
+ }
166
+
167
+ internals.validate(schema.$_root, desc);
168
+ return desc;
169
+ };
170
+
171
+
172
+ internals.describe = function (item, options = {}) {
173
+
174
+ if (Array.isArray(item)) {
175
+ return item.map(internals.describe);
176
+ }
177
+
178
+ if (item === Common.symbols.deepDefault) {
179
+ return { special: 'deep' };
180
+ }
181
+
182
+ if (typeof item !== 'object' ||
183
+ item === null) {
184
+
185
+ return item;
186
+ }
187
+
188
+ if (options.assign === 'options') {
189
+ return clone(item);
190
+ }
191
+
192
+ if (Buffer && Buffer.isBuffer(item)) { // $lab:coverage:ignore$
193
+ return { buffer: item.toString('binary') };
194
+ }
195
+
196
+ if (item instanceof Date) {
197
+ return item.toISOString();
198
+ }
199
+
200
+ if (item instanceof Error) {
201
+ return item;
202
+ }
203
+
204
+ if (item instanceof RegExp) {
205
+ if (options.assign === 'regex') {
206
+ return item.toString();
207
+ }
208
+
209
+ return { regex: item.toString() };
210
+ }
211
+
212
+ if (item[Common.symbols.literal]) {
213
+ return { function: item.literal };
214
+ }
215
+
216
+ if (typeof item.describe === 'function') {
217
+ if (options.assign === 'ref') {
218
+ return item.describe().ref;
219
+ }
220
+
221
+ return item.describe();
222
+ }
223
+
224
+ const normalized = {};
225
+ for (const key in item) {
226
+ const value = item[key];
227
+ if (value === undefined) {
228
+ continue;
229
+ }
230
+
231
+ normalized[key] = internals.describe(value, { assign: key });
232
+ }
233
+
234
+ return normalized;
235
+ };
236
+
237
+
238
+ exports.build = function (joi, desc) {
239
+
240
+ const builder = new internals.Builder(joi);
241
+ return builder.parse(desc);
242
+ };
243
+
244
+
245
+ internals.Builder = class {
246
+
247
+ constructor(joi) {
248
+
249
+ this.joi = joi;
250
+ }
251
+
252
+ parse(desc) {
253
+
254
+ internals.validate(this.joi, desc);
255
+
256
+ // Type
257
+
258
+ let schema = this.joi[desc.type]()._bare();
259
+ const def = schema._definition;
260
+
261
+ // Flags
262
+
263
+ if (desc.flags) {
264
+ for (const flag in desc.flags) {
265
+ const setter = def.flags[flag] && def.flags[flag].setter || flag;
266
+ assert(typeof schema[setter] === 'function', 'Invalid flag', flag, 'for type', desc.type);
267
+ schema = schema[setter](this.build(desc.flags[flag]));
268
+ }
269
+ }
270
+
271
+ // Preferences
272
+
273
+ if (desc.preferences) {
274
+ schema = schema.preferences(this.build(desc.preferences));
275
+ }
276
+
277
+ // Allow / Invalid
278
+
279
+ if (desc.allow) {
280
+ schema = schema.allow(...this.build(desc.allow));
281
+ }
282
+
283
+ if (desc.invalid) {
284
+ schema = schema.invalid(...this.build(desc.invalid));
285
+ }
286
+
287
+ // Rules
288
+
289
+ if (desc.rules) {
290
+ for (const rule of desc.rules) {
291
+ assert(typeof schema[rule.name] === 'function', 'Invalid rule', rule.name, 'for type', desc.type);
292
+
293
+ const args = [];
294
+ if (rule.args) {
295
+ const built = {};
296
+ for (const key in rule.args) {
297
+ built[key] = this.build(rule.args[key], { assign: key });
298
+ }
299
+
300
+ const keys = Object.keys(built);
301
+ const definition = def.rules[rule.name].args;
302
+ if (definition) {
303
+ assert(keys.length <= definition.length, 'Invalid number of arguments for', desc.type, rule.name, '(expected up to', definition.length, ', found', keys.length, ')');
304
+ for (const { name } of definition) {
305
+ args.push(built[name]);
306
+ }
307
+ }
308
+ else {
309
+ assert(keys.length === 1, 'Invalid number of arguments for', desc.type, rule.name, '(expected up to 1, found', keys.length, ')');
310
+ args.push(built[keys[0]]);
311
+ }
312
+ }
313
+
314
+ // Apply
315
+
316
+ schema = schema[rule.name](...args);
317
+
318
+ // Ruleset
319
+
320
+ const options = {};
321
+ for (const custom in def.modifiers) {
322
+ if (rule[custom] !== undefined) {
323
+ options[custom] = this.build(rule[custom]);
324
+ }
325
+ }
326
+
327
+ if (Object.keys(options).length) {
328
+ schema = schema.rule(options);
329
+ }
330
+ }
331
+ }
332
+
333
+ // Terms
334
+
335
+ const terms = {};
336
+ for (const key in desc) {
337
+ if (['allow', 'flags', 'invalid', 'whens', 'preferences', 'rules', 'type'].includes(key)) {
338
+ continue;
339
+ }
340
+
341
+ assert(def.terms[key], 'Term', key, 'missing configuration');
342
+ const manifest = def.terms[key].manifest;
343
+
344
+ if (manifest === 'schema') {
345
+ terms[key] = desc[key].map((item) => this.parse(item));
346
+ continue;
347
+ }
348
+
349
+ if (manifest === 'values') {
350
+ terms[key] = desc[key].map((item) => this.build(item));
351
+ continue;
352
+ }
353
+
354
+ if (manifest === 'single') {
355
+ terms[key] = this.build(desc[key]);
356
+ continue;
357
+ }
358
+
359
+ if (typeof manifest === 'object') {
360
+ terms[key] = {};
361
+ for (const name in desc[key]) {
362
+ const value = desc[key][name];
363
+ terms[key][name] = this.parse(value);
364
+ }
365
+
366
+ continue;
367
+ }
368
+
369
+ terms[key] = this.build(desc[key]);
370
+ }
371
+
372
+ if (desc.whens) {
373
+ terms.whens = desc.whens.map((when) => this.build(when));
374
+ }
375
+
376
+ schema = def.manifest.build(schema, terms);
377
+ schema.$_temp.ruleset = false;
378
+ return schema;
379
+ }
380
+
381
+ build(desc, options = {}) {
382
+
383
+ if (desc === null) {
384
+ return null;
385
+ }
386
+
387
+ if (Array.isArray(desc)) {
388
+ return desc.map((item) => this.build(item));
389
+ }
390
+
391
+ if (desc instanceof Error) {
392
+ return desc;
393
+ }
394
+
395
+ if (options.assign === 'options') {
396
+ return clone(desc);
397
+ }
398
+
399
+ if (options.assign === 'regex') {
400
+ return internals.regex(desc);
401
+ }
402
+
403
+ if (options.assign === 'ref') {
404
+ return Ref.build(desc);
405
+ }
406
+
407
+ if (typeof desc !== 'object') {
408
+ return desc;
409
+ }
410
+
411
+ if (Object.keys(desc).length === 1) {
412
+ if (desc.buffer) {
413
+ assert(Buffer, 'Buffers are not supported');
414
+ return Buffer && Buffer.from(desc.buffer, 'binary'); // $lab:coverage:ignore$
415
+ }
416
+
417
+ if (desc.function) {
418
+ return { [Common.symbols.literal]: true, literal: desc.function };
419
+ }
420
+
421
+ if (desc.override) {
422
+ return Common.symbols.override;
423
+ }
424
+
425
+ if (desc.ref) {
426
+ return Ref.build(desc.ref);
427
+ }
428
+
429
+ if (desc.regex) {
430
+ return internals.regex(desc.regex);
431
+ }
432
+
433
+ if (desc.special) {
434
+ assert(['deep'].includes(desc.special), 'Unknown special value', desc.special);
435
+ return Common.symbols.deepDefault;
436
+ }
437
+
438
+ if (desc.value) {
439
+ return clone(desc.value);
440
+ }
441
+ }
442
+
443
+ if (desc.type) {
444
+ return this.parse(desc);
445
+ }
446
+
447
+ if (desc.template) {
448
+ return Template.build(desc);
449
+ }
450
+
451
+ const normalized = {};
452
+ for (const key in desc) {
453
+ normalized[key] = this.build(desc[key], { assign: key });
454
+ }
455
+
456
+ return normalized;
457
+ }
458
+ };
459
+
460
+
461
+ internals.regex = function (string) {
462
+
463
+ const end = string.lastIndexOf('/');
464
+ const exp = string.slice(1, end);
465
+ const flags = string.slice(end + 1);
466
+ return new RegExp(exp, flags);
467
+ };
468
+
469
+
470
+ internals.validate = function (joi, desc) {
471
+
472
+ Schemas = Schemas || require('./schemas');
473
+
474
+ joi.assert(desc, Schemas.description);
475
+ };
@@ -0,0 +1,177 @@
1
+ 'use strict';
2
+
3
+ const { assert, clone } = require('@hapi/hoek');
4
+
5
+ const Template = require('./template');
6
+
7
+
8
+ const internals = {};
9
+
10
+
11
+ exports.compile = function (messages, target) {
12
+
13
+ // Single value string ('plain error message', 'template {error} message')
14
+
15
+ if (typeof messages === 'string') {
16
+ assert(!target, 'Cannot set single message string');
17
+ return new Template(messages);
18
+ }
19
+
20
+ // Single value template
21
+
22
+ if (Template.isTemplate(messages)) {
23
+ assert(!target, 'Cannot set single message template');
24
+ return messages;
25
+ }
26
+
27
+ // By error code { 'number.min': <string | template> }
28
+
29
+ assert(typeof messages === 'object' && !Array.isArray(messages), 'Invalid message options');
30
+
31
+ target = target ? clone(target) : {};
32
+
33
+ for (let code in messages) {
34
+ const message = messages[code];
35
+
36
+ if (code === 'root' ||
37
+ Template.isTemplate(message)) {
38
+
39
+ target[code] = message;
40
+ continue;
41
+ }
42
+
43
+ if (typeof message === 'string') {
44
+ target[code] = new Template(message);
45
+ continue;
46
+ }
47
+
48
+ // By language { english: { 'number.min': <string | template> } }
49
+
50
+ assert(typeof message === 'object' && !Array.isArray(message), 'Invalid message for', code);
51
+
52
+ const language = code;
53
+ target[language] = target[language] || {};
54
+
55
+ for (code in message) {
56
+ const localized = message[code];
57
+
58
+ if (code === 'root' ||
59
+ Template.isTemplate(localized)) {
60
+
61
+ target[language][code] = localized;
62
+ continue;
63
+ }
64
+
65
+ assert(typeof localized === 'string', 'Invalid message for', code, 'in', language);
66
+ target[language][code] = new Template(localized);
67
+ }
68
+ }
69
+
70
+ return target;
71
+ };
72
+
73
+
74
+ exports.decompile = function (messages) {
75
+
76
+ // By error code { 'number.min': <string | template> }
77
+
78
+ const target = {};
79
+ for (let code in messages) {
80
+ const message = messages[code];
81
+
82
+ if (code === 'root') {
83
+ target.root = message;
84
+ continue;
85
+ }
86
+
87
+ if (Template.isTemplate(message)) {
88
+ target[code] = message.describe({ compact: true });
89
+ continue;
90
+ }
91
+
92
+ // By language { english: { 'number.min': <string | template> } }
93
+
94
+ const language = code;
95
+ target[language] = {};
96
+
97
+ for (code in message) {
98
+ const localized = message[code];
99
+
100
+ if (code === 'root') {
101
+ target[language].root = localized;
102
+ continue;
103
+ }
104
+
105
+ target[language][code] = localized.describe({ compact: true });
106
+ }
107
+ }
108
+
109
+ return target;
110
+ };
111
+
112
+
113
+ exports.merge = function (base, extended) {
114
+
115
+ if (!base) {
116
+ return exports.compile(extended);
117
+ }
118
+
119
+ if (!extended) {
120
+ return base;
121
+ }
122
+
123
+ // Single value string
124
+
125
+ if (typeof extended === 'string') {
126
+ return new Template(extended);
127
+ }
128
+
129
+ // Single value template
130
+
131
+ if (Template.isTemplate(extended)) {
132
+ return extended;
133
+ }
134
+
135
+ // By error code { 'number.min': <string | template> }
136
+
137
+ const target = clone(base);
138
+
139
+ for (let code in extended) {
140
+ const message = extended[code];
141
+
142
+ if (code === 'root' ||
143
+ Template.isTemplate(message)) {
144
+
145
+ target[code] = message;
146
+ continue;
147
+ }
148
+
149
+ if (typeof message === 'string') {
150
+ target[code] = new Template(message);
151
+ continue;
152
+ }
153
+
154
+ // By language { english: { 'number.min': <string | template> } }
155
+
156
+ assert(typeof message === 'object' && !Array.isArray(message), 'Invalid message for', code);
157
+
158
+ const language = code;
159
+ target[language] = target[language] || {};
160
+
161
+ for (code in message) {
162
+ const localized = message[code];
163
+
164
+ if (code === 'root' ||
165
+ Template.isTemplate(localized)) {
166
+
167
+ target[language][code] = localized;
168
+ continue;
169
+ }
170
+
171
+ assert(typeof localized === 'string', 'Invalid message for', code, 'in', language);
172
+ target[language][code] = new Template(localized);
173
+ }
174
+ }
175
+
176
+ return target;
177
+ };