joi 18.0.2 → 18.1.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/dist/joi-browser.min.js +1 -1
- package/lib/base.js +172 -1
- package/lib/common.js +19 -0
- package/lib/extend.js +6 -0
- package/lib/index.d.ts +9 -2
- package/lib/schemas.js +3 -1
- package/lib/types/alternatives.js +43 -0
- package/lib/types/array.js +106 -0
- package/lib/types/binary.js +24 -0
- package/lib/types/date.js +48 -2
- package/lib/types/keys.js +80 -0
- package/lib/types/link.js +23 -0
- package/lib/types/number.js +36 -0
- package/lib/types/string.js +104 -0
- package/lib/types/symbol.js +12 -0
- package/package.json +3 -2
package/lib/types/keys.js
CHANGED
|
@@ -47,6 +47,70 @@ module.exports = Any.extend({
|
|
|
47
47
|
return schema.keys(keys);
|
|
48
48
|
},
|
|
49
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
|
+
|
|
50
114
|
validate(value, { schema, error, state, prefs }) {
|
|
51
115
|
|
|
52
116
|
if (!value ||
|
|
@@ -278,6 +342,12 @@ module.exports = Any.extend({
|
|
|
278
342
|
|
|
279
343
|
return helpers.error('object.' + name, { limit: args.limit, value });
|
|
280
344
|
},
|
|
345
|
+
jsonSchema(rule, res) {
|
|
346
|
+
|
|
347
|
+
res.minProperties = rule.args.limit;
|
|
348
|
+
res.maxProperties = rule.args.limit;
|
|
349
|
+
return res;
|
|
350
|
+
},
|
|
281
351
|
args: [
|
|
282
352
|
{
|
|
283
353
|
name: 'limit',
|
|
@@ -292,6 +362,11 @@ module.exports = Any.extend({
|
|
|
292
362
|
method(limit) {
|
|
293
363
|
|
|
294
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;
|
|
295
370
|
}
|
|
296
371
|
},
|
|
297
372
|
|
|
@@ -299,6 +374,11 @@ module.exports = Any.extend({
|
|
|
299
374
|
method(limit) {
|
|
300
375
|
|
|
301
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;
|
|
302
382
|
}
|
|
303
383
|
},
|
|
304
384
|
|
package/lib/types/link.js
CHANGED
|
@@ -29,6 +29,29 @@ module.exports = Any.extend({
|
|
|
29
29
|
return schema.ref(ref);
|
|
30
30
|
},
|
|
31
31
|
|
|
32
|
+
jsonSchema(schema, res, mode, options) {
|
|
33
|
+
|
|
34
|
+
if (!schema.$_terms.link) {
|
|
35
|
+
return res;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { ref } = schema.$_terms.link[0];
|
|
39
|
+
|
|
40
|
+
if (ref.ancestor === 'root' || ref.ancestor > 0) {
|
|
41
|
+
res.$ref = `#/${ref.path.map((p) => `properties/${p}`).join('/')}`;
|
|
42
|
+
return res;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (ref.path.length === 1) {
|
|
46
|
+
res.$ref = `#/$defs/${ref.path[0]}`;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
res.$ref = `#/${ref.path.slice(1).map((p) => `properties/${p}`).join('/')}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return res;
|
|
53
|
+
},
|
|
54
|
+
|
|
32
55
|
validate(value, { schema, state, prefs }) {
|
|
33
56
|
|
|
34
57
|
assert(schema.$_terms.link, 'Uninitialized link schema');
|
package/lib/types/number.js
CHANGED
|
@@ -136,6 +136,11 @@ module.exports = Any.extend({
|
|
|
136
136
|
method(limit) {
|
|
137
137
|
|
|
138
138
|
return this.$_addRule({ name: 'greater', method: 'compare', args: { limit }, operator: '>' });
|
|
139
|
+
},
|
|
140
|
+
jsonSchema(rule, res) {
|
|
141
|
+
|
|
142
|
+
res.exclusiveMinimum = rule.args.limit;
|
|
143
|
+
return res;
|
|
139
144
|
}
|
|
140
145
|
},
|
|
141
146
|
|
|
@@ -151,6 +156,12 @@ module.exports = Any.extend({
|
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
return helpers.error('number.integer');
|
|
159
|
+
},
|
|
160
|
+
jsonSchema(rule, res) {
|
|
161
|
+
|
|
162
|
+
res.type = 'integer';
|
|
163
|
+
|
|
164
|
+
return res;
|
|
154
165
|
}
|
|
155
166
|
},
|
|
156
167
|
|
|
@@ -158,6 +169,11 @@ module.exports = Any.extend({
|
|
|
158
169
|
method(limit) {
|
|
159
170
|
|
|
160
171
|
return this.$_addRule({ name: 'less', method: 'compare', args: { limit }, operator: '<' });
|
|
172
|
+
},
|
|
173
|
+
jsonSchema(rule, res) {
|
|
174
|
+
|
|
175
|
+
res.exclusiveMaximum = rule.args.limit;
|
|
176
|
+
return res;
|
|
161
177
|
}
|
|
162
178
|
},
|
|
163
179
|
|
|
@@ -165,6 +181,11 @@ module.exports = Any.extend({
|
|
|
165
181
|
method(limit) {
|
|
166
182
|
|
|
167
183
|
return this.$_addRule({ name: 'max', method: 'compare', args: { limit }, operator: '<=' });
|
|
184
|
+
},
|
|
185
|
+
jsonSchema(rule, res) {
|
|
186
|
+
|
|
187
|
+
res.maximum = rule.args.limit;
|
|
188
|
+
return res;
|
|
168
189
|
}
|
|
169
190
|
},
|
|
170
191
|
|
|
@@ -172,6 +193,11 @@ module.exports = Any.extend({
|
|
|
172
193
|
method(limit) {
|
|
173
194
|
|
|
174
195
|
return this.$_addRule({ name: 'min', method: 'compare', args: { limit }, operator: '>=' });
|
|
196
|
+
},
|
|
197
|
+
jsonSchema(rule, res) {
|
|
198
|
+
|
|
199
|
+
res.minimum = rule.args.limit;
|
|
200
|
+
return res;
|
|
175
201
|
}
|
|
176
202
|
},
|
|
177
203
|
|
|
@@ -203,6 +229,11 @@ module.exports = Any.extend({
|
|
|
203
229
|
value :
|
|
204
230
|
helpers.error('number.multiple', { multiple: options.args.base, value });
|
|
205
231
|
},
|
|
232
|
+
jsonSchema(rule, res) {
|
|
233
|
+
|
|
234
|
+
res.multipleOf = rule.args.base;
|
|
235
|
+
return res;
|
|
236
|
+
},
|
|
206
237
|
args: [
|
|
207
238
|
{
|
|
208
239
|
name: 'base',
|
|
@@ -284,6 +315,11 @@ module.exports = Any.extend({
|
|
|
284
315
|
}
|
|
285
316
|
|
|
286
317
|
return helpers.error(`number.${sign}`);
|
|
318
|
+
},
|
|
319
|
+
jsonSchema(rule, res) {
|
|
320
|
+
|
|
321
|
+
res['x-constraint'] = { ...res['x-constraint'], sign: rule.args.sign };
|
|
322
|
+
return res;
|
|
287
323
|
}
|
|
288
324
|
},
|
|
289
325
|
|
package/lib/types/string.js
CHANGED
|
@@ -144,6 +144,23 @@ module.exports = Any.extend({
|
|
|
144
144
|
}
|
|
145
145
|
},
|
|
146
146
|
|
|
147
|
+
jsonSchema(schema, res, mode, options) {
|
|
148
|
+
|
|
149
|
+
const noEmpty = !schema._valids?.has('') && !schema._flags.only;
|
|
150
|
+
if (noEmpty) {
|
|
151
|
+
const min = schema.$_getRule('min');
|
|
152
|
+
const length = schema.$_getRule('length');
|
|
153
|
+
|
|
154
|
+
if ((!min || min.args.limit > 0) &&
|
|
155
|
+
(!length || length.args.limit > 0)) {
|
|
156
|
+
|
|
157
|
+
res.minLength = 1;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return res;
|
|
162
|
+
},
|
|
163
|
+
|
|
147
164
|
rules: {
|
|
148
165
|
|
|
149
166
|
alphanum: {
|
|
@@ -180,6 +197,11 @@ module.exports = Any.extend({
|
|
|
180
197
|
}
|
|
181
198
|
|
|
182
199
|
return helpers.error('string.base64');
|
|
200
|
+
},
|
|
201
|
+
jsonSchema(rule, res) {
|
|
202
|
+
|
|
203
|
+
res.format = 'base64';
|
|
204
|
+
return res;
|
|
183
205
|
}
|
|
184
206
|
},
|
|
185
207
|
|
|
@@ -260,6 +282,11 @@ module.exports = Any.extend({
|
|
|
260
282
|
}
|
|
261
283
|
|
|
262
284
|
return helpers.error('string.dataUri');
|
|
285
|
+
},
|
|
286
|
+
jsonSchema(rule, res) {
|
|
287
|
+
|
|
288
|
+
res.format = 'data-uri';
|
|
289
|
+
return res;
|
|
263
290
|
}
|
|
264
291
|
},
|
|
265
292
|
|
|
@@ -309,6 +336,11 @@ module.exports = Any.extend({
|
|
|
309
336
|
}
|
|
310
337
|
|
|
311
338
|
return helpers.error('string.email', { value, invalids });
|
|
339
|
+
},
|
|
340
|
+
jsonSchema(rule, res) {
|
|
341
|
+
|
|
342
|
+
res.format = 'email';
|
|
343
|
+
return res;
|
|
312
344
|
}
|
|
313
345
|
},
|
|
314
346
|
|
|
@@ -398,6 +430,11 @@ module.exports = Any.extend({
|
|
|
398
430
|
}
|
|
399
431
|
|
|
400
432
|
return value;
|
|
433
|
+
},
|
|
434
|
+
jsonSchema(rule, res) {
|
|
435
|
+
|
|
436
|
+
res.format = 'uuid';
|
|
437
|
+
return res;
|
|
401
438
|
}
|
|
402
439
|
},
|
|
403
440
|
|
|
@@ -430,6 +467,11 @@ module.exports = Any.extend({
|
|
|
430
467
|
}
|
|
431
468
|
|
|
432
469
|
return value;
|
|
470
|
+
},
|
|
471
|
+
jsonSchema(rule, res) {
|
|
472
|
+
|
|
473
|
+
res.format = 'hex';
|
|
474
|
+
return res;
|
|
433
475
|
}
|
|
434
476
|
},
|
|
435
477
|
|
|
@@ -447,6 +489,11 @@ module.exports = Any.extend({
|
|
|
447
489
|
}
|
|
448
490
|
|
|
449
491
|
return helpers.error('string.hostname');
|
|
492
|
+
},
|
|
493
|
+
jsonSchema(rule, res) {
|
|
494
|
+
|
|
495
|
+
res.format = 'hostname';
|
|
496
|
+
return res;
|
|
450
497
|
}
|
|
451
498
|
},
|
|
452
499
|
|
|
@@ -477,6 +524,18 @@ module.exports = Any.extend({
|
|
|
477
524
|
}
|
|
478
525
|
|
|
479
526
|
return helpers.error('string.ip', { value, cidr: options.cidr });
|
|
527
|
+
},
|
|
528
|
+
jsonSchema(rule, res) {
|
|
529
|
+
|
|
530
|
+
const version = rule.args.options.version;
|
|
531
|
+
if (version && version.length === 1) {
|
|
532
|
+
res.format = version[0];
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
res.format = 'ip';
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return res;
|
|
480
539
|
}
|
|
481
540
|
},
|
|
482
541
|
|
|
@@ -492,6 +551,11 @@ module.exports = Any.extend({
|
|
|
492
551
|
}
|
|
493
552
|
|
|
494
553
|
return error('string.isoDate');
|
|
554
|
+
},
|
|
555
|
+
jsonSchema(rule, res) {
|
|
556
|
+
|
|
557
|
+
res.format = 'date-time';
|
|
558
|
+
return res;
|
|
495
559
|
}
|
|
496
560
|
},
|
|
497
561
|
|
|
@@ -507,6 +571,11 @@ module.exports = Any.extend({
|
|
|
507
571
|
}
|
|
508
572
|
|
|
509
573
|
return helpers.error('string.isoDuration');
|
|
574
|
+
},
|
|
575
|
+
jsonSchema(rule, res) {
|
|
576
|
+
|
|
577
|
+
res.format = 'duration';
|
|
578
|
+
return res;
|
|
510
579
|
}
|
|
511
580
|
},
|
|
512
581
|
|
|
@@ -524,6 +593,12 @@ module.exports = Any.extend({
|
|
|
524
593
|
|
|
525
594
|
return helpers.error('string.' + name, { limit: args.limit, value, encoding });
|
|
526
595
|
},
|
|
596
|
+
jsonSchema(rule, res) {
|
|
597
|
+
|
|
598
|
+
res.minLength = rule.args.limit;
|
|
599
|
+
res.maxLength = rule.args.limit;
|
|
600
|
+
return res;
|
|
601
|
+
},
|
|
527
602
|
args: [
|
|
528
603
|
{
|
|
529
604
|
name: 'limit',
|
|
@@ -547,6 +622,11 @@ module.exports = Any.extend({
|
|
|
547
622
|
|
|
548
623
|
return internals.length(this, 'max', limit, '<=', encoding);
|
|
549
624
|
},
|
|
625
|
+
jsonSchema(rule, res) {
|
|
626
|
+
|
|
627
|
+
res.maxLength = rule.args.limit;
|
|
628
|
+
return res;
|
|
629
|
+
},
|
|
550
630
|
args: ['limit', 'encoding']
|
|
551
631
|
},
|
|
552
632
|
|
|
@@ -555,6 +635,15 @@ module.exports = Any.extend({
|
|
|
555
635
|
|
|
556
636
|
return internals.length(this, 'min', limit, '>=', encoding);
|
|
557
637
|
},
|
|
638
|
+
jsonSchema(rule, res) {
|
|
639
|
+
|
|
640
|
+
if (rule.args.limit > 0) {
|
|
641
|
+
|
|
642
|
+
res.minLength = rule.args.limit;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return res;
|
|
646
|
+
},
|
|
558
647
|
args: ['limit', 'encoding']
|
|
559
648
|
},
|
|
560
649
|
|
|
@@ -602,6 +691,11 @@ module.exports = Any.extend({
|
|
|
602
691
|
|
|
603
692
|
return helpers.error(errorCode, { name: options.name, regex, value });
|
|
604
693
|
},
|
|
694
|
+
jsonSchema(rule, res) {
|
|
695
|
+
|
|
696
|
+
res.pattern = rule.args.regex.source;
|
|
697
|
+
return res;
|
|
698
|
+
},
|
|
605
699
|
args: ['regex', 'options'],
|
|
606
700
|
multi: true
|
|
607
701
|
},
|
|
@@ -639,6 +733,11 @@ module.exports = Any.extend({
|
|
|
639
733
|
}
|
|
640
734
|
|
|
641
735
|
return helpers.error('string.token');
|
|
736
|
+
},
|
|
737
|
+
jsonSchema(rule, res) {
|
|
738
|
+
|
|
739
|
+
res.format = 'token';
|
|
740
|
+
return res;
|
|
642
741
|
}
|
|
643
742
|
},
|
|
644
743
|
|
|
@@ -728,6 +827,11 @@ module.exports = Any.extend({
|
|
|
728
827
|
}
|
|
729
828
|
|
|
730
829
|
return helpers.error('string.uri');
|
|
830
|
+
},
|
|
831
|
+
jsonSchema(rule, res) {
|
|
832
|
+
|
|
833
|
+
res.format = 'uri';
|
|
834
|
+
return res;
|
|
731
835
|
}
|
|
732
836
|
}
|
|
733
837
|
},
|
package/lib/types/symbol.js
CHANGED
|
@@ -95,6 +95,18 @@ module.exports = Any.extend({
|
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
97
|
|
|
98
|
+
jsonSchema(schema, json, mode, options) {
|
|
99
|
+
|
|
100
|
+
const map = schema.$_terms.map;
|
|
101
|
+
if (!map.size) {
|
|
102
|
+
return {};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
anyOf: Array.from(map.keys()).map((key) => ({ const: key }))
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
|
|
98
110
|
messages: {
|
|
99
111
|
'symbol.base': '{{#label}} must be a symbol',
|
|
100
112
|
'symbol.map': '{{#label}} must be one of {{#map}}'
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "joi",
|
|
3
3
|
"description": "Object schema validation",
|
|
4
|
-
"version": "18.0
|
|
4
|
+
"version": "18.1.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "git://github.com/hapijs/joi",
|
|
7
7
|
"type": "git"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@hapi/pinpoint": "^2.0.1",
|
|
28
28
|
"@hapi/tlds": "^1.1.1",
|
|
29
29
|
"@hapi/topo": "^6.0.2",
|
|
30
|
-
"@standard-schema/spec": "^1.
|
|
30
|
+
"@standard-schema/spec": "^1.1.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@hapi/bourne": "^3.0.0",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@hapi/joi-legacy-test": "npm:@hapi/joi@15.x.x",
|
|
37
37
|
"@hapi/lab": "^26.0.0",
|
|
38
38
|
"@types/node": "^20.17.47",
|
|
39
|
+
"ajv": "^8.18.0",
|
|
39
40
|
"typescript": "^5.8.3"
|
|
40
41
|
},
|
|
41
42
|
"scripts": {
|