json-2-joi 3.0.2 → 4.0.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/LICENSE.txt +203 -203
- package/README.md +361 -361
- package/index.js +14 -14
- package/lib/resolver.js +699 -699
- package/package.json +53 -53
package/lib/resolver.js
CHANGED
|
@@ -1,699 +1,699 @@
|
|
|
1
|
-
const Joi = require('joi');
|
|
2
|
-
const Hoek = require('@hapi/hoek');
|
|
3
|
-
const Bourne = require('@hapi/bourne');
|
|
4
|
-
const debugCreator = require('debug');
|
|
5
|
-
|
|
6
|
-
const debug = debugCreator('enjoi');
|
|
7
|
-
|
|
8
|
-
debugCreator.formatters.f = function (f) {
|
|
9
|
-
return f();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const schemaSchema = Joi.alternatives(Joi.object().unknown(true), Joi.string()).required();
|
|
13
|
-
const extensionSchema = Joi.alternatives().try(Joi.object().unknown(true), Joi.function())
|
|
14
|
-
|
|
15
|
-
const optionsSchema = Joi.object({
|
|
16
|
-
subSchemas: Joi.object().unknown(true).default({}),
|
|
17
|
-
extensions: Joi.array().items(extensionSchema).default([]),
|
|
18
|
-
refineType: Joi.func().allow(null),
|
|
19
|
-
refineSchema: Joi.func().allow(null),
|
|
20
|
-
refineDescription: Joi.func().allow(null),
|
|
21
|
-
allowNull: Joi.boolean().default(false),
|
|
22
|
-
forbidArrayNull: Joi.boolean().default(true),
|
|
23
|
-
strictArrayRequired: Joi.boolean().default(false),
|
|
24
|
-
strictRequired: Joi.boolean().default(false),
|
|
25
|
-
strictEnum: Joi.boolean().default(true),
|
|
26
|
-
enableEnum: Joi.boolean().default(true),
|
|
27
|
-
customizedNullValues: Joi.array().items(Joi.any()).default([null]).min(1),
|
|
28
|
-
joiOptions: Joi.object().unknown(true).default({})
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const overridableOptions = {
|
|
32
|
-
refineType: Joi.func().allow(null),
|
|
33
|
-
refineSchema: Joi.func().allow(null),
|
|
34
|
-
refineDescription: Joi.func().allow(null),
|
|
35
|
-
allowNull: Joi.boolean(),
|
|
36
|
-
forbidArrayNull: Joi.boolean(),
|
|
37
|
-
strictArrayRequired: Joi.boolean(),
|
|
38
|
-
strictRequired: Joi.boolean(),
|
|
39
|
-
noDefaults: Joi.boolean(),
|
|
40
|
-
strictEnum: Joi.boolean(),
|
|
41
|
-
enableEnum: Joi.boolean(),
|
|
42
|
-
customizedNullValues: Joi.array().items(Joi.any()).min(1),
|
|
43
|
-
};
|
|
44
|
-
const overridableOptionKeys = Object.keys(overridableOptions);
|
|
45
|
-
const overrideOptionsSchema = Joi.object(overridableOptions);
|
|
46
|
-
|
|
47
|
-
function mergeOverridableOptions(target, ...sources) {
|
|
48
|
-
sources.forEach((src) => {
|
|
49
|
-
overridableOptionKeys.forEach((key) => {
|
|
50
|
-
if (typeof src[key] !== 'undefined') {
|
|
51
|
-
target[key] = src[key];
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
});
|
|
55
|
-
return target;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function isNumber(value) {
|
|
59
|
-
return typeof value === 'number';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function isObject(value) {
|
|
63
|
-
return value !== null && typeof value === 'object';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function isNotEmptyObject(value) {
|
|
67
|
-
return isObject(value) && Object.keys(value).length > 0;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function allowNullIfNeeded(schema, { allowNull = false, forbidArrayNull = true, customizedNullValues = [null] }) {
|
|
71
|
-
if (allowNull) {
|
|
72
|
-
const definition = schema.describe();
|
|
73
|
-
if (definition.flags && definition.flags.only && definition.allow) {
|
|
74
|
-
// Already has enum restriction
|
|
75
|
-
return schema;
|
|
76
|
-
}
|
|
77
|
-
if (!schema.type && (schema.allOf || schema.anyOf || schema.oneOf || schema.not)) {
|
|
78
|
-
return schema;
|
|
79
|
-
}
|
|
80
|
-
if (forbidArrayNull && schema.type === 'array') {
|
|
81
|
-
return schema;
|
|
82
|
-
}
|
|
83
|
-
return schema.allow(...customizedNullValues);
|
|
84
|
-
}
|
|
85
|
-
return schema;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function addEnumRestriction(joiSchema, enumList, { allowNull = false, strictEnum = true, enableEnum = true }) {
|
|
89
|
-
if (!enableEnum || !Array.isArray(enumList) || enumList.length === 0) {
|
|
90
|
-
return joiSchema;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const valids = [].concat(enumList);
|
|
94
|
-
if (allowNull && !strictEnum) {
|
|
95
|
-
if (joiSchema.type === 'string') {
|
|
96
|
-
valids.push('');
|
|
97
|
-
}
|
|
98
|
-
valids.push(null);
|
|
99
|
-
}
|
|
100
|
-
return joiSchema.valid(...valids);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function normalizedId(id) {
|
|
104
|
-
return id.replace(/[^a-zA-Z0-9]/g, '_');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function getJoiInstance(joiOptions, extensions, joiInstance) {
|
|
108
|
-
if (joiInstance) {
|
|
109
|
-
return joiInstance;
|
|
110
|
-
}
|
|
111
|
-
if (isNotEmptyObject(joiOptions)) {
|
|
112
|
-
joiInstance = Joi.defaults((schema) => schema.options(joiOptions));
|
|
113
|
-
} else {
|
|
114
|
-
joiInstance = Joi;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return joiInstance.extend(
|
|
118
|
-
{
|
|
119
|
-
type: 'object',
|
|
120
|
-
base: joiInstance.object(),
|
|
121
|
-
coerce: {
|
|
122
|
-
from: 'string',
|
|
123
|
-
method(value) {
|
|
124
|
-
if (typeof value !== 'string' || (value[0] !== '{' && !/^\s*\{/.test(value))) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
try {
|
|
128
|
-
return { value: Bourne.parse(value) };
|
|
129
|
-
} catch (ignoreErr) { /* eslint-disable-line no-unused-vars */
|
|
130
|
-
// Purposefully ignoring error
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
type: 'array',
|
|
137
|
-
base: joiInstance.array(),
|
|
138
|
-
coerce: {
|
|
139
|
-
from: 'string',
|
|
140
|
-
method(value) {
|
|
141
|
-
if (typeof value !== 'string' || (value[0] !== '[' && !/^\s*\[/.test(value))) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
try {
|
|
145
|
-
return { value: Bourne.parse(value) };
|
|
146
|
-
} catch (ignoreErr) { /* eslint-disable-line no-unused-vars */
|
|
147
|
-
// Purposefully ignoring error
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
...extensions
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const mapToObj = m => {
|
|
157
|
-
return Array.from(m).reduce((obj, [key, value]) => {
|
|
158
|
-
obj[key] = Joi.isSchema(value) ? value.describe() : value;
|
|
159
|
-
return obj;
|
|
160
|
-
}, {});
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
class SchemaResolver {
|
|
164
|
-
constructor(options, joiInstance) {
|
|
165
|
-
const validatedResult = optionsSchema.validate(options);
|
|
166
|
-
|
|
167
|
-
Hoek.assert(!validatedResult.error, validatedResult.error);
|
|
168
|
-
|
|
169
|
-
const { subSchemas = {}, extensions = [], joiOptions } = validatedResult.value;
|
|
170
|
-
|
|
171
|
-
this.resolveOptions = mergeOverridableOptions({}, validatedResult.value);
|
|
172
|
-
this.subSchemas = subSchemas;
|
|
173
|
-
this.joiOptions = joiOptions;
|
|
174
|
-
this.joiSharedSchemas = new Map();
|
|
175
|
-
this.resolveOptions.noDefaults = this.joiOptions.noDefaults;
|
|
176
|
-
|
|
177
|
-
this.joi = getJoiInstance(joiOptions, extensions, joiInstance);
|
|
178
|
-
|
|
179
|
-
for (let sharedSchemaId in this.subSchemas) {
|
|
180
|
-
const sharedSchema = this.subSchemas[sharedSchemaId];
|
|
181
|
-
if (typeof sharedSchema === 'object' && !sharedSchema.$id) {
|
|
182
|
-
// shorthand schema type does not need to set $id
|
|
183
|
-
sharedSchema.$id = sharedSchemaId;
|
|
184
|
-
}
|
|
185
|
-
this.joiSharedSchemas.set(sharedSchemaId, this.resolve(sharedSchema, this.resolveOptions));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const itr = this.joiSharedSchemas.keys();
|
|
189
|
-
this.initialSharedSchemaIds = [];
|
|
190
|
-
for (const id of itr) {
|
|
191
|
-
this.initialSharedSchemaIds.push(id);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
resolveShareSchemas(schema, resolveOptions, sharedProperties = '$defs', baseUri = '') {
|
|
196
|
-
const localShared = schema[sharedProperties]
|
|
197
|
-
if (localShared) {
|
|
198
|
-
for (let sharedSchemaId in localShared) {
|
|
199
|
-
this.joiSharedSchemas.set(
|
|
200
|
-
`${baseUri}#/${sharedProperties}/${sharedSchemaId}`,
|
|
201
|
-
this.resolve(localShared[sharedSchemaId], resolveOptions, baseUri)
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
convert(schema, options = {}) {
|
|
208
|
-
const validateSchema = schemaSchema.validate(schema);
|
|
209
|
-
const { value: overrideOptions, error: overrideOptionsError } = overrideOptionsSchema.validate(options);
|
|
210
|
-
|
|
211
|
-
Hoek.assert(!validateSchema.error, validateSchema.error);
|
|
212
|
-
Hoek.assert(!overrideOptionsError, overrideOptionsError);
|
|
213
|
-
|
|
214
|
-
const schemaId = schema.$id || '';
|
|
215
|
-
this.subSchemas[schemaId] = schema;
|
|
216
|
-
|
|
217
|
-
const joiSchema = this.resolve(schema, mergeOverridableOptions({}, this.resolveOptions, overrideOptions));
|
|
218
|
-
|
|
219
|
-
// Cleanup all shared schema registered other than the initial shared ones
|
|
220
|
-
delete this.subSchemas[schemaId];
|
|
221
|
-
const itr = this.joiSharedSchemas.keys();
|
|
222
|
-
for (const id of itr) {
|
|
223
|
-
if (!this.initialSharedSchemaIds.includes(id)) {
|
|
224
|
-
delete this.subSchemas[id];
|
|
225
|
-
this.joiSharedSchemas.delete(id);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return joiSchema;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
resolve(schema, resolveOptions = {}, baseUri = (schema.$id || ''), isPropertyRequired = false) {
|
|
232
|
-
if (this.joi.isSchema(schema)) {
|
|
233
|
-
return schema;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const schemaId = schema.$id;
|
|
237
|
-
let resolvedSchema;
|
|
238
|
-
|
|
239
|
-
if (schemaId) {
|
|
240
|
-
baseUri = schemaId;
|
|
241
|
-
if (!this.subSchemas[schemaId]) {
|
|
242
|
-
this.subSchemas[schemaId] = schema;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
this.resolveShareSchemas(schema, resolveOptions, '$defs', baseUri);
|
|
247
|
-
this.resolveShareSchemas(schema, resolveOptions, 'definitions', baseUri);
|
|
248
|
-
|
|
249
|
-
if (typeof schema === 'string') {
|
|
250
|
-
// If schema is itself a string, interpret it as a type
|
|
251
|
-
resolvedSchema = this.resolveType({ type: schema }, resolveOptions, baseUri, isPropertyRequired);
|
|
252
|
-
} else if (schema.$ref) {
|
|
253
|
-
if (schema.$ref.trim() === '#') {
|
|
254
|
-
return baseUri ? this.joi.link(`#${normalizedId(baseUri)}`) : this.joi.link('/');
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
resolvedSchema = this.resolve(this.resolveReference(schema.$ref, baseUri), resolveOptions, baseUri, isPropertyRequired);
|
|
258
|
-
} else {
|
|
259
|
-
const partialSchemas = [];
|
|
260
|
-
if (schema.type) {
|
|
261
|
-
partialSchemas.push(this.resolveType(schema, resolveOptions, baseUri, isPropertyRequired));
|
|
262
|
-
} else if (schema.properties) {
|
|
263
|
-
// if no type is specified, just properties
|
|
264
|
-
partialSchemas.push(this.object(schema, resolveOptions, baseUri))
|
|
265
|
-
} else if (schema.format) {
|
|
266
|
-
// if no type is specified, just format
|
|
267
|
-
partialSchemas.push(this.string(schema))
|
|
268
|
-
} else if (schema.enum) {
|
|
269
|
-
// If no type is specified, just enum
|
|
270
|
-
partialSchemas.push(
|
|
271
|
-
addEnumRestriction(this.joi.any().example(schema.enum[0]), schema.enum, resolveOptions)
|
|
272
|
-
);
|
|
273
|
-
} else if (schema.const) {
|
|
274
|
-
// Constant value
|
|
275
|
-
partialSchemas.push(this.getValueJoiType(schema.const).valid(schema.const));
|
|
276
|
-
}
|
|
277
|
-
if (schema.anyOf) {
|
|
278
|
-
partialSchemas.push(this.resolveAnyOf(schema, resolveOptions, baseUri));
|
|
279
|
-
}
|
|
280
|
-
if (schema.allOf) {
|
|
281
|
-
partialSchemas.push(this.resolveAllOf(schema, resolveOptions, baseUri));
|
|
282
|
-
}
|
|
283
|
-
if (schema.oneOf) {
|
|
284
|
-
partialSchemas.push(this.resolveOneOf(schema, resolveOptions, baseUri));
|
|
285
|
-
}
|
|
286
|
-
if (schema.not) {
|
|
287
|
-
partialSchemas.push(this.resolveNot(schema, resolveOptions, baseUri));
|
|
288
|
-
}
|
|
289
|
-
if (partialSchemas.length === 0) {
|
|
290
|
-
//Fall through to whatever.
|
|
291
|
-
console.warn('WARNING: schema missing a \'type\' or \'$ref\' or \'enum\': \n%s', JSON.stringify(schema, null, 2));
|
|
292
|
-
//TODO: Handle better
|
|
293
|
-
partialSchemas.push(this.joi.any());
|
|
294
|
-
}
|
|
295
|
-
resolvedSchema = partialSchemas.length === 1 ? partialSchemas[0] : this.joi.alternatives(partialSchemas).match('all');
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (resolveOptions.refineSchema) {
|
|
299
|
-
resolvedSchema = resolveOptions.refineSchema(resolvedSchema, schema);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (schema.default !== undefined && !resolveOptions.noDefaults) {
|
|
303
|
-
resolvedSchema = resolvedSchema.default(schema.default)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
resolvedSchema = this.copyMetaAnnotations(schema, resolvedSchema);
|
|
307
|
-
|
|
308
|
-
if (schemaId && !(this.joiSharedSchemas.get(schemaId))) {
|
|
309
|
-
resolvedSchema = resolvedSchema.id(normalizedId(schemaId));
|
|
310
|
-
this.joiSharedSchemas.set(schemaId, resolvedSchema);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (schema.$anchor) {
|
|
314
|
-
this.joiSharedSchemas.set(`${baseUri}#${schema.$anchor}`, resolvedSchema);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (isPropertyRequired) {
|
|
318
|
-
resolvedSchema = resolvedSchema.required();
|
|
319
|
-
if (resolveOptions.strictRequired) {
|
|
320
|
-
resolvedSchema = resolvedSchema.invalid(null, '');
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
resolvedSchema = allowNullIfNeeded(resolvedSchema, resolveOptions);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return resolvedSchema;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
resolveReference(value, baseUri = '') {
|
|
330
|
-
debug('----- Resolving schema reference: %s with baseUri: %s ----- ', value, baseUri);
|
|
331
|
-
debug('Registered JOI schema for shared: %f', () => {
|
|
332
|
-
return JSON.stringify(mapToObj(this.joiSharedSchemas), null, 2);
|
|
333
|
-
});
|
|
334
|
-
const hashIdx = value.indexOf('#');
|
|
335
|
-
let refSchema;
|
|
336
|
-
|
|
337
|
-
if (hashIdx === -1) {
|
|
338
|
-
// Locate through $ref: 'refId'
|
|
339
|
-
// debug('# Resolving reference through id: %s', value);
|
|
340
|
-
refSchema = this.joiSharedSchemas.get(value);
|
|
341
|
-
} else {
|
|
342
|
-
const id = value.substring(0, hashIdx);
|
|
343
|
-
const path = value.substring(hashIdx + 1);
|
|
344
|
-
|
|
345
|
-
const canonicalURI = id ? value : `${baseUri}${value}`;
|
|
346
|
-
|
|
347
|
-
if (canonicalURI.indexOf('/') === -1) {
|
|
348
|
-
// Locate through $ref: [baseUri]#anchorSchema
|
|
349
|
-
// debug('# Resolving reference through $anchor: %s', canonicalURI);
|
|
350
|
-
refSchema = this.joiSharedSchemas.get(canonicalURI);
|
|
351
|
-
} else if (canonicalURI.indexOf('/properties') === -1) {
|
|
352
|
-
// Locate through $ref: [baseUri]#/$defs/shared
|
|
353
|
-
// debug('# Resolving reference through direct canonicalURI: %s', canonicalURI);
|
|
354
|
-
refSchema = this.joiSharedSchemas.get(canonicalURI);
|
|
355
|
-
} else {
|
|
356
|
-
// Locate through $ref: [baseUri]#/$defs/shared/properties/level1 or [baseUri]#/properties/product
|
|
357
|
-
// Can only get raw schema and build Joi again because it might not completely built yet
|
|
358
|
-
// debug('# Resolving reference through relative path: baseUri - %s; id - %s; path - %s', baseUri, id, path);
|
|
359
|
-
|
|
360
|
-
const paths = path.split('/');
|
|
361
|
-
refSchema = this.subSchemas[id || baseUri];
|
|
362
|
-
|
|
363
|
-
while (paths.length > 0 && refSchema) {
|
|
364
|
-
if (paths[0]) {
|
|
365
|
-
refSchema = refSchema[paths[0]];
|
|
366
|
-
}
|
|
367
|
-
paths.splice(0, 1);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
Hoek.assert(refSchema, `Can not find schema reference: ${value} with baseUri: ${baseUri}`);
|
|
373
|
-
|
|
374
|
-
return refSchema;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
resolveType(schema, resolveOptions, baseUri, isPropertyRequired = false) {
|
|
378
|
-
let joischema;
|
|
379
|
-
|
|
380
|
-
const joitype = (type, options, format) => {
|
|
381
|
-
let joischema;
|
|
382
|
-
|
|
383
|
-
if (options.refineType) {
|
|
384
|
-
type = options.refineType(type, format);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
switch (type) {
|
|
388
|
-
case 'array':
|
|
389
|
-
joischema = this.array(schema, options, baseUri, isPropertyRequired);
|
|
390
|
-
break;
|
|
391
|
-
case 'boolean':
|
|
392
|
-
joischema = this.joi.boolean();
|
|
393
|
-
break;
|
|
394
|
-
case 'integer':
|
|
395
|
-
case 'number':
|
|
396
|
-
joischema = this.number(schema);
|
|
397
|
-
break;
|
|
398
|
-
case 'object':
|
|
399
|
-
joischema = this.object(schema, options, baseUri);
|
|
400
|
-
break;
|
|
401
|
-
case 'string':
|
|
402
|
-
joischema = this.string(schema);
|
|
403
|
-
break;
|
|
404
|
-
case 'null':
|
|
405
|
-
joischema = this.joi.any().valid(...options.customizedNullValues);
|
|
406
|
-
break;
|
|
407
|
-
default:
|
|
408
|
-
joischema = this.joi.types()[type];
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
Hoek.assert(joischema, 'Could not resolve type: ' + schema.type + '.');
|
|
412
|
-
|
|
413
|
-
return joischema;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
let nullIdx;
|
|
417
|
-
if (Array.isArray(schema.type)) {
|
|
418
|
-
nullIdx = schema.type.findIndex((type) => type === 'null');
|
|
419
|
-
if (nullIdx > -1) {
|
|
420
|
-
schema.type.splice(nullIdx, 1);
|
|
421
|
-
}
|
|
422
|
-
if (schema.type.length === 1) {
|
|
423
|
-
schema.type = schema.type[0];
|
|
424
|
-
} else if (schema.type.length === 0) {
|
|
425
|
-
schema.type = 'null';
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (Array.isArray(schema.type)) {
|
|
430
|
-
const schemas = [];
|
|
431
|
-
|
|
432
|
-
for (let i = 0; i < schema.type.length; i++) {
|
|
433
|
-
schemas.push(joitype(schema.type[i], resolveOptions, schema.format));
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
joischema = this.joi.alternatives(schemas);
|
|
437
|
-
} else {
|
|
438
|
-
joischema = joitype(schema.type, resolveOptions, schema.format);
|
|
439
|
-
}
|
|
440
|
-
if (nullIdx > -1) {
|
|
441
|
-
joischema = allowNullIfNeeded(joischema,
|
|
442
|
-
{ allowNull: true, customizedNullValues: resolveOptions.customizedNullValues });
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const typeDefinitionMap = {
|
|
446
|
-
title: 'label'
|
|
447
|
-
};
|
|
448
|
-
Object.keys(typeDefinitionMap).forEach(function (key) {
|
|
449
|
-
if (schema[key] !== undefined) {
|
|
450
|
-
joischema = joischema[typeDefinitionMap[key]](schema[key]);
|
|
451
|
-
}
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
let desc = schema.description;
|
|
455
|
-
if (resolveOptions.refineDescription) {
|
|
456
|
-
desc = resolveOptions.refineDescription(schema);
|
|
457
|
-
}
|
|
458
|
-
if (desc) {
|
|
459
|
-
joischema = joischema.description(desc);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
let example;
|
|
463
|
-
if (schema.examples && schema.examples.length > 0) {
|
|
464
|
-
example = schema.examples[0];
|
|
465
|
-
}
|
|
466
|
-
if (!example && typeof schema.default !== 'undefined') {
|
|
467
|
-
example = schema.default;
|
|
468
|
-
}
|
|
469
|
-
if (!example && schema.enum && schema.enum.length > 0) {
|
|
470
|
-
example = schema.enum[0];
|
|
471
|
-
}
|
|
472
|
-
if (example) {
|
|
473
|
-
joischema = joischema.type === 'array' && !Array.isArray(example) ? joischema.example([example]) : joischema.example(example);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return addEnumRestriction(joischema, schema.enum, resolveOptions);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
copyMetaAnnotations(schema, joischema) {
|
|
480
|
-
const metaDataNames = ['title', 'deprecated', 'readOnly', 'writeOnly'];
|
|
481
|
-
const metas = {};
|
|
482
|
-
for (const metaName of metaDataNames) {
|
|
483
|
-
if (typeof schema[metaName] !== 'undefined') {
|
|
484
|
-
metas[metaName] = schema[metaName];
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (Object.keys(metas).length === 0) {
|
|
489
|
-
return joischema;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return joischema.meta(metas);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
resolveOneOf(schema, resolveOptions, baseUri) {
|
|
496
|
-
Hoek.assert(Array.isArray(schema.oneOf), 'Expected oneOf to be an array.');
|
|
497
|
-
|
|
498
|
-
return this.joi.alternatives(schema.oneOf.map(schema => this.resolve(schema, resolveOptions, baseUri))).match('one');
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
resolveAnyOf(schema, resolveOptions, baseUri) {
|
|
502
|
-
Hoek.assert(Array.isArray(schema.anyOf), 'Expected anyOf to be an array.');
|
|
503
|
-
|
|
504
|
-
return this.joi.alternatives(schema.anyOf.map(schema => this.resolve(schema, resolveOptions, baseUri))).match('any');
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
resolveAllOf(schema, resolveOptions, baseUri) {
|
|
508
|
-
Hoek.assert(Array.isArray(schema.allOf), 'Expected allOf to be an array.');
|
|
509
|
-
|
|
510
|
-
return this.joi.alternatives(schema.allOf.map(schema => this.resolve(schema, resolveOptions, baseUri))).match('all');
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
resolveNot(schema, resolveOptions, baseUri) {
|
|
514
|
-
Hoek.assert(isObject(schema.not), 'Expected Not to be an object.');
|
|
515
|
-
|
|
516
|
-
return this.joi.alternatives().conditional(
|
|
517
|
-
'.',
|
|
518
|
-
{
|
|
519
|
-
not: this.resolve(schema.not, resolveOptions, baseUri),
|
|
520
|
-
then: this.joi.any(),
|
|
521
|
-
otherwise: this.joi.any().forbidden()
|
|
522
|
-
}
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
resolveProperties(jsonSchema, resolveOptions, baseUri) {
|
|
527
|
-
const schemas = {};
|
|
528
|
-
|
|
529
|
-
if (!isObject(jsonSchema.properties)) {
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
Object.keys(jsonSchema.properties).forEach((key) => {
|
|
534
|
-
const property = jsonSchema.properties[key];
|
|
535
|
-
const isPropertyRequired = Array.isArray(jsonSchema.required) && !!~jsonSchema.required.indexOf(key);
|
|
536
|
-
|
|
537
|
-
const joischema = this.resolve(property, resolveOptions, baseUri, isPropertyRequired);
|
|
538
|
-
|
|
539
|
-
schemas[key] = joischema;
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
return schemas;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
object(schema, resolveOptions, baseUri) {
|
|
546
|
-
let joischema = this.joi.object(this.resolveProperties(schema, resolveOptions, baseUri));
|
|
547
|
-
|
|
548
|
-
if (isObject(schema.additionalProperties)) {
|
|
549
|
-
joischema = joischema.pattern(/^/, this.resolve(schema.additionalProperties, resolveOptions, baseUri));
|
|
550
|
-
} else if (typeof this.joiOptions.allowUnknown === 'undefined' || typeof schema.additionalProperties !== 'undefined') {
|
|
551
|
-
joischema = joischema.unknown(schema.additionalProperties !== false);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
isNumber(schema.minProperties) && (joischema = joischema.min(schema.minProperties));
|
|
555
|
-
isNumber(schema.maxProperties) && (joischema = joischema.max(schema.maxProperties));
|
|
556
|
-
|
|
557
|
-
return joischema;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
resolveAsArray(value, resolveOptions, baseUri, isPropertyRequired = false) {
|
|
561
|
-
return [].concat(value).map((v) => {
|
|
562
|
-
const vSchema = this.resolve(v, resolveOptions, baseUri);
|
|
563
|
-
if (resolveOptions.strictArrayRequired && isPropertyRequired) {
|
|
564
|
-
return vSchema.invalid(...resolveOptions.customizedNullValues);
|
|
565
|
-
}
|
|
566
|
-
return allowNullIfNeeded(vSchema, resolveOptions)
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
array(schema, resolveOptions, baseUri, isPropertyRequired = false) {
|
|
571
|
-
let joischema = this.joi.array();
|
|
572
|
-
let items;
|
|
573
|
-
|
|
574
|
-
if (schema.items) {
|
|
575
|
-
items = this.resolveAsArray(schema.items, resolveOptions, baseUri, isPropertyRequired);
|
|
576
|
-
|
|
577
|
-
joischema = joischema.items(...items);
|
|
578
|
-
} else if (schema.ordered) {
|
|
579
|
-
items = this.resolveAsArray(schema.ordered, resolveOptions, baseUri, isPropertyRequired);
|
|
580
|
-
joischema = joischema.ordered(...items);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
if (items && schema.additionalItems === false) {
|
|
584
|
-
joischema = joischema.max(items.length);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (isPropertyRequired && resolveOptions.strictArrayRequired && !schema.minItems) {
|
|
588
|
-
schema.minItems = 1;
|
|
589
|
-
}
|
|
590
|
-
isNumber(schema.minItems) && (joischema = joischema.min(schema.minItems));
|
|
591
|
-
isNumber(schema.maxItems) && (joischema = joischema.max(schema.maxItems));
|
|
592
|
-
|
|
593
|
-
if (schema.uniqueItems) {
|
|
594
|
-
joischema = joischema.unique();
|
|
595
|
-
}
|
|
596
|
-
if (schema.contains) {
|
|
597
|
-
joischema = joischema.has(this.resolve(schema.contains, resolveOptions, baseUri));
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return joischema;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
number(schema) {
|
|
604
|
-
let joischema = this.joi.number();
|
|
605
|
-
|
|
606
|
-
if (schema.type === 'integer') {
|
|
607
|
-
joischema = joischema.integer();
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
isNumber(schema.minimum) && (joischema = joischema.min(schema.minimum));
|
|
611
|
-
isNumber(schema.maximum) && (joischema = joischema.max(schema.maximum));
|
|
612
|
-
isNumber(schema.exclusiveMinimum) && (joischema = joischema.greater(schema.exclusiveMinimum));
|
|
613
|
-
isNumber(schema.exclusiveMaximum) && (joischema = joischema.less(schema.exclusiveMaximum));
|
|
614
|
-
isNumber(schema.multipleOf) && schema.multipleOf !== 0 && (joischema = joischema.multiple(schema.multipleOf));
|
|
615
|
-
|
|
616
|
-
return joischema;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
string(schema) {
|
|
620
|
-
let joischema = this.joi.string();
|
|
621
|
-
|
|
622
|
-
const dateRegex = '(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])';
|
|
623
|
-
const timeRegex = '([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(.[0-9]+)?(Z|(\\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))';
|
|
624
|
-
const dateTimeRegex = dateRegex + 'T' + timeRegex;
|
|
625
|
-
|
|
626
|
-
switch (schema.format) {
|
|
627
|
-
case 'date':
|
|
628
|
-
return joischema.regex(new RegExp('^' + dateRegex + '$', 'i'), 'JsonSchema date format');
|
|
629
|
-
case 'time':
|
|
630
|
-
return joischema.regex(new RegExp('^' + timeRegex + '$', 'i'), 'JsonSchema time format');
|
|
631
|
-
case 'date-time':
|
|
632
|
-
return joischema.regex(new RegExp('^' + dateTimeRegex + '$', 'i'), 'JsonSchema date-time format');
|
|
633
|
-
case 'binary':
|
|
634
|
-
joischema = this.binary(schema);
|
|
635
|
-
break;
|
|
636
|
-
case 'email':
|
|
637
|
-
return joischema.email();
|
|
638
|
-
case 'hostname':
|
|
639
|
-
return joischema.hostname();
|
|
640
|
-
case 'ipv4':
|
|
641
|
-
return joischema.ip({
|
|
642
|
-
version: ['ipv4']
|
|
643
|
-
});
|
|
644
|
-
case 'ipv6':
|
|
645
|
-
return joischema.ip({
|
|
646
|
-
version: ['ipv6']
|
|
647
|
-
});
|
|
648
|
-
case 'uri':
|
|
649
|
-
return joischema.uri();
|
|
650
|
-
case 'byte':
|
|
651
|
-
joischema = joischema.base64();
|
|
652
|
-
break;
|
|
653
|
-
case 'uuid':
|
|
654
|
-
return joischema.guid({ version: ['uuidv4'] });
|
|
655
|
-
case 'guid':
|
|
656
|
-
return joischema.guid();
|
|
657
|
-
}
|
|
658
|
-
return this.regularString(schema, joischema);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
regularString(schema, joischema) {
|
|
662
|
-
schema.pattern && (joischema = joischema.regex(new RegExp(schema.pattern)));
|
|
663
|
-
|
|
664
|
-
if ((typeof schema.minLength === 'undefined' || schema.minLength === 0) && !schema.pattern && !schema.format && !schema.enum) {
|
|
665
|
-
joischema = joischema.allow('');
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
isNumber(schema.minLength) && (joischema = joischema.min(schema.minLength));
|
|
669
|
-
isNumber(schema.maxLength) && (joischema = joischema.max(schema.maxLength));
|
|
670
|
-
return joischema;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
binary(schema) {
|
|
674
|
-
let joischema = this.joi.binary();
|
|
675
|
-
isNumber(schema.minLength) && (joischema = joischema.min(schema.minLength));
|
|
676
|
-
isNumber(schema.maxLength) && (joischema = joischema.max(schema.maxLength));
|
|
677
|
-
return joischema;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
getValueJoiType(value) {
|
|
681
|
-
let joiType;
|
|
682
|
-
switch (typeof value) {
|
|
683
|
-
case 'string':
|
|
684
|
-
joiType = this.joi.string();
|
|
685
|
-
break;
|
|
686
|
-
case 'number':
|
|
687
|
-
joiType = this.joi.number();
|
|
688
|
-
break;
|
|
689
|
-
case 'boolean':
|
|
690
|
-
joiType = this.joi.boolean();
|
|
691
|
-
break;
|
|
692
|
-
default:
|
|
693
|
-
joiType = this.joi.any();
|
|
694
|
-
}
|
|
695
|
-
return joiType;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
module.exports = SchemaResolver;
|
|
1
|
+
const Joi = require('joi');
|
|
2
|
+
const Hoek = require('@hapi/hoek');
|
|
3
|
+
const Bourne = require('@hapi/bourne');
|
|
4
|
+
const debugCreator = require('debug');
|
|
5
|
+
|
|
6
|
+
const debug = debugCreator('enjoi');
|
|
7
|
+
|
|
8
|
+
debugCreator.formatters.f = function (f) {
|
|
9
|
+
return f();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const schemaSchema = Joi.alternatives(Joi.object().unknown(true), Joi.string()).required();
|
|
13
|
+
const extensionSchema = Joi.alternatives().try(Joi.object().unknown(true), Joi.function())
|
|
14
|
+
|
|
15
|
+
const optionsSchema = Joi.object({
|
|
16
|
+
subSchemas: Joi.object().unknown(true).default({}),
|
|
17
|
+
extensions: Joi.array().items(extensionSchema).default([]),
|
|
18
|
+
refineType: Joi.func().allow(null),
|
|
19
|
+
refineSchema: Joi.func().allow(null),
|
|
20
|
+
refineDescription: Joi.func().allow(null),
|
|
21
|
+
allowNull: Joi.boolean().default(false),
|
|
22
|
+
forbidArrayNull: Joi.boolean().default(true),
|
|
23
|
+
strictArrayRequired: Joi.boolean().default(false),
|
|
24
|
+
strictRequired: Joi.boolean().default(false),
|
|
25
|
+
strictEnum: Joi.boolean().default(true),
|
|
26
|
+
enableEnum: Joi.boolean().default(true),
|
|
27
|
+
customizedNullValues: Joi.array().items(Joi.any()).default([null]).min(1),
|
|
28
|
+
joiOptions: Joi.object().unknown(true).default({})
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const overridableOptions = {
|
|
32
|
+
refineType: Joi.func().allow(null),
|
|
33
|
+
refineSchema: Joi.func().allow(null),
|
|
34
|
+
refineDescription: Joi.func().allow(null),
|
|
35
|
+
allowNull: Joi.boolean(),
|
|
36
|
+
forbidArrayNull: Joi.boolean(),
|
|
37
|
+
strictArrayRequired: Joi.boolean(),
|
|
38
|
+
strictRequired: Joi.boolean(),
|
|
39
|
+
noDefaults: Joi.boolean(),
|
|
40
|
+
strictEnum: Joi.boolean(),
|
|
41
|
+
enableEnum: Joi.boolean(),
|
|
42
|
+
customizedNullValues: Joi.array().items(Joi.any()).min(1),
|
|
43
|
+
};
|
|
44
|
+
const overridableOptionKeys = Object.keys(overridableOptions);
|
|
45
|
+
const overrideOptionsSchema = Joi.object(overridableOptions);
|
|
46
|
+
|
|
47
|
+
function mergeOverridableOptions(target, ...sources) {
|
|
48
|
+
sources.forEach((src) => {
|
|
49
|
+
overridableOptionKeys.forEach((key) => {
|
|
50
|
+
if (typeof src[key] !== 'undefined') {
|
|
51
|
+
target[key] = src[key];
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
return target;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isNumber(value) {
|
|
59
|
+
return typeof value === 'number';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isObject(value) {
|
|
63
|
+
return value !== null && typeof value === 'object';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isNotEmptyObject(value) {
|
|
67
|
+
return isObject(value) && Object.keys(value).length > 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function allowNullIfNeeded(schema, { allowNull = false, forbidArrayNull = true, customizedNullValues = [null] }) {
|
|
71
|
+
if (allowNull) {
|
|
72
|
+
const definition = schema.describe();
|
|
73
|
+
if (definition.flags && definition.flags.only && definition.allow) {
|
|
74
|
+
// Already has enum restriction
|
|
75
|
+
return schema;
|
|
76
|
+
}
|
|
77
|
+
if (!schema.type && (schema.allOf || schema.anyOf || schema.oneOf || schema.not)) {
|
|
78
|
+
return schema;
|
|
79
|
+
}
|
|
80
|
+
if (forbidArrayNull && schema.type === 'array') {
|
|
81
|
+
return schema;
|
|
82
|
+
}
|
|
83
|
+
return schema.allow(...customizedNullValues);
|
|
84
|
+
}
|
|
85
|
+
return schema;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function addEnumRestriction(joiSchema, enumList, { allowNull = false, strictEnum = true, enableEnum = true }) {
|
|
89
|
+
if (!enableEnum || !Array.isArray(enumList) || enumList.length === 0) {
|
|
90
|
+
return joiSchema;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const valids = [].concat(enumList);
|
|
94
|
+
if (allowNull && !strictEnum) {
|
|
95
|
+
if (joiSchema.type === 'string') {
|
|
96
|
+
valids.push('');
|
|
97
|
+
}
|
|
98
|
+
valids.push(null);
|
|
99
|
+
}
|
|
100
|
+
return joiSchema.valid(...valids);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizedId(id) {
|
|
104
|
+
return id.replace(/[^a-zA-Z0-9]/g, '_');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getJoiInstance(joiOptions, extensions, joiInstance) {
|
|
108
|
+
if (joiInstance) {
|
|
109
|
+
return joiInstance;
|
|
110
|
+
}
|
|
111
|
+
if (isNotEmptyObject(joiOptions)) {
|
|
112
|
+
joiInstance = Joi.defaults((schema) => schema.options(joiOptions));
|
|
113
|
+
} else {
|
|
114
|
+
joiInstance = Joi;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return joiInstance.extend(
|
|
118
|
+
{
|
|
119
|
+
type: 'object',
|
|
120
|
+
base: joiInstance.object(),
|
|
121
|
+
coerce: {
|
|
122
|
+
from: 'string',
|
|
123
|
+
method(value) {
|
|
124
|
+
if (typeof value !== 'string' || (value[0] !== '{' && !/^\s*\{/.test(value))) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
return { value: Bourne.parse(value) };
|
|
129
|
+
} catch (ignoreErr) { /* eslint-disable-line no-unused-vars */
|
|
130
|
+
// Purposefully ignoring error
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: 'array',
|
|
137
|
+
base: joiInstance.array(),
|
|
138
|
+
coerce: {
|
|
139
|
+
from: 'string',
|
|
140
|
+
method(value) {
|
|
141
|
+
if (typeof value !== 'string' || (value[0] !== '[' && !/^\s*\[/.test(value))) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
return { value: Bourne.parse(value) };
|
|
146
|
+
} catch (ignoreErr) { /* eslint-disable-line no-unused-vars */
|
|
147
|
+
// Purposefully ignoring error
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
...extensions
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const mapToObj = m => {
|
|
157
|
+
return Array.from(m).reduce((obj, [key, value]) => {
|
|
158
|
+
obj[key] = Joi.isSchema(value) ? value.describe() : value;
|
|
159
|
+
return obj;
|
|
160
|
+
}, {});
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
class SchemaResolver {
|
|
164
|
+
constructor(options, joiInstance) {
|
|
165
|
+
const validatedResult = optionsSchema.validate(options);
|
|
166
|
+
|
|
167
|
+
Hoek.assert(!validatedResult.error, validatedResult.error);
|
|
168
|
+
|
|
169
|
+
const { subSchemas = {}, extensions = [], joiOptions } = validatedResult.value;
|
|
170
|
+
|
|
171
|
+
this.resolveOptions = mergeOverridableOptions({}, validatedResult.value);
|
|
172
|
+
this.subSchemas = subSchemas;
|
|
173
|
+
this.joiOptions = joiOptions;
|
|
174
|
+
this.joiSharedSchemas = new Map();
|
|
175
|
+
this.resolveOptions.noDefaults = this.joiOptions.noDefaults;
|
|
176
|
+
|
|
177
|
+
this.joi = getJoiInstance(joiOptions, extensions, joiInstance);
|
|
178
|
+
|
|
179
|
+
for (let sharedSchemaId in this.subSchemas) {
|
|
180
|
+
const sharedSchema = this.subSchemas[sharedSchemaId];
|
|
181
|
+
if (typeof sharedSchema === 'object' && !sharedSchema.$id) {
|
|
182
|
+
// shorthand schema type does not need to set $id
|
|
183
|
+
sharedSchema.$id = sharedSchemaId;
|
|
184
|
+
}
|
|
185
|
+
this.joiSharedSchemas.set(sharedSchemaId, this.resolve(sharedSchema, this.resolveOptions));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const itr = this.joiSharedSchemas.keys();
|
|
189
|
+
this.initialSharedSchemaIds = [];
|
|
190
|
+
for (const id of itr) {
|
|
191
|
+
this.initialSharedSchemaIds.push(id);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
resolveShareSchemas(schema, resolveOptions, sharedProperties = '$defs', baseUri = '') {
|
|
196
|
+
const localShared = schema[sharedProperties]
|
|
197
|
+
if (localShared) {
|
|
198
|
+
for (let sharedSchemaId in localShared) {
|
|
199
|
+
this.joiSharedSchemas.set(
|
|
200
|
+
`${baseUri}#/${sharedProperties}/${sharedSchemaId}`,
|
|
201
|
+
this.resolve(localShared[sharedSchemaId], resolveOptions, baseUri)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
convert(schema, options = {}) {
|
|
208
|
+
const validateSchema = schemaSchema.validate(schema);
|
|
209
|
+
const { value: overrideOptions, error: overrideOptionsError } = overrideOptionsSchema.validate(options);
|
|
210
|
+
|
|
211
|
+
Hoek.assert(!validateSchema.error, validateSchema.error);
|
|
212
|
+
Hoek.assert(!overrideOptionsError, overrideOptionsError);
|
|
213
|
+
|
|
214
|
+
const schemaId = schema.$id || '';
|
|
215
|
+
this.subSchemas[schemaId] = schema;
|
|
216
|
+
|
|
217
|
+
const joiSchema = this.resolve(schema, mergeOverridableOptions({}, this.resolveOptions, overrideOptions));
|
|
218
|
+
|
|
219
|
+
// Cleanup all shared schema registered other than the initial shared ones
|
|
220
|
+
delete this.subSchemas[schemaId];
|
|
221
|
+
const itr = this.joiSharedSchemas.keys();
|
|
222
|
+
for (const id of itr) {
|
|
223
|
+
if (!this.initialSharedSchemaIds.includes(id)) {
|
|
224
|
+
delete this.subSchemas[id];
|
|
225
|
+
this.joiSharedSchemas.delete(id);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return joiSchema;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
resolve(schema, resolveOptions = {}, baseUri = (schema.$id || ''), isPropertyRequired = false) {
|
|
232
|
+
if (this.joi.isSchema(schema)) {
|
|
233
|
+
return schema;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const schemaId = schema.$id;
|
|
237
|
+
let resolvedSchema;
|
|
238
|
+
|
|
239
|
+
if (schemaId) {
|
|
240
|
+
baseUri = schemaId;
|
|
241
|
+
if (!this.subSchemas[schemaId]) {
|
|
242
|
+
this.subSchemas[schemaId] = schema;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.resolveShareSchemas(schema, resolveOptions, '$defs', baseUri);
|
|
247
|
+
this.resolveShareSchemas(schema, resolveOptions, 'definitions', baseUri);
|
|
248
|
+
|
|
249
|
+
if (typeof schema === 'string') {
|
|
250
|
+
// If schema is itself a string, interpret it as a type
|
|
251
|
+
resolvedSchema = this.resolveType({ type: schema }, resolveOptions, baseUri, isPropertyRequired);
|
|
252
|
+
} else if (schema.$ref) {
|
|
253
|
+
if (schema.$ref.trim() === '#') {
|
|
254
|
+
return baseUri ? this.joi.link(`#${normalizedId(baseUri)}`) : this.joi.link('/');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
resolvedSchema = this.resolve(this.resolveReference(schema.$ref, baseUri), resolveOptions, baseUri, isPropertyRequired);
|
|
258
|
+
} else {
|
|
259
|
+
const partialSchemas = [];
|
|
260
|
+
if (schema.type) {
|
|
261
|
+
partialSchemas.push(this.resolveType(schema, resolveOptions, baseUri, isPropertyRequired));
|
|
262
|
+
} else if (schema.properties) {
|
|
263
|
+
// if no type is specified, just properties
|
|
264
|
+
partialSchemas.push(this.object(schema, resolveOptions, baseUri))
|
|
265
|
+
} else if (schema.format) {
|
|
266
|
+
// if no type is specified, just format
|
|
267
|
+
partialSchemas.push(this.string(schema))
|
|
268
|
+
} else if (schema.enum) {
|
|
269
|
+
// If no type is specified, just enum
|
|
270
|
+
partialSchemas.push(
|
|
271
|
+
addEnumRestriction(this.joi.any().example(schema.enum[0]), schema.enum, resolveOptions)
|
|
272
|
+
);
|
|
273
|
+
} else if (schema.const) {
|
|
274
|
+
// Constant value
|
|
275
|
+
partialSchemas.push(this.getValueJoiType(schema.const).valid(schema.const));
|
|
276
|
+
}
|
|
277
|
+
if (schema.anyOf) {
|
|
278
|
+
partialSchemas.push(this.resolveAnyOf(schema, resolveOptions, baseUri));
|
|
279
|
+
}
|
|
280
|
+
if (schema.allOf) {
|
|
281
|
+
partialSchemas.push(this.resolveAllOf(schema, resolveOptions, baseUri));
|
|
282
|
+
}
|
|
283
|
+
if (schema.oneOf) {
|
|
284
|
+
partialSchemas.push(this.resolveOneOf(schema, resolveOptions, baseUri));
|
|
285
|
+
}
|
|
286
|
+
if (schema.not) {
|
|
287
|
+
partialSchemas.push(this.resolveNot(schema, resolveOptions, baseUri));
|
|
288
|
+
}
|
|
289
|
+
if (partialSchemas.length === 0) {
|
|
290
|
+
//Fall through to whatever.
|
|
291
|
+
console.warn('WARNING: schema missing a \'type\' or \'$ref\' or \'enum\': \n%s', JSON.stringify(schema, null, 2));
|
|
292
|
+
//TODO: Handle better
|
|
293
|
+
partialSchemas.push(this.joi.any());
|
|
294
|
+
}
|
|
295
|
+
resolvedSchema = partialSchemas.length === 1 ? partialSchemas[0] : this.joi.alternatives(partialSchemas).match('all');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (resolveOptions.refineSchema) {
|
|
299
|
+
resolvedSchema = resolveOptions.refineSchema(resolvedSchema, schema);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (schema.default !== undefined && !resolveOptions.noDefaults) {
|
|
303
|
+
resolvedSchema = resolvedSchema.default(schema.default)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
resolvedSchema = this.copyMetaAnnotations(schema, resolvedSchema);
|
|
307
|
+
|
|
308
|
+
if (schemaId && !(this.joiSharedSchemas.get(schemaId))) {
|
|
309
|
+
resolvedSchema = resolvedSchema.id(normalizedId(schemaId));
|
|
310
|
+
this.joiSharedSchemas.set(schemaId, resolvedSchema);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (schema.$anchor) {
|
|
314
|
+
this.joiSharedSchemas.set(`${baseUri}#${schema.$anchor}`, resolvedSchema);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (isPropertyRequired) {
|
|
318
|
+
resolvedSchema = resolvedSchema.required();
|
|
319
|
+
if (resolveOptions.strictRequired) {
|
|
320
|
+
resolvedSchema = resolvedSchema.invalid(null, '');
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
resolvedSchema = allowNullIfNeeded(resolvedSchema, resolveOptions);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return resolvedSchema;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
resolveReference(value, baseUri = '') {
|
|
330
|
+
debug('----- Resolving schema reference: %s with baseUri: %s ----- ', value, baseUri);
|
|
331
|
+
debug('Registered JOI schema for shared: %f', () => {
|
|
332
|
+
return JSON.stringify(mapToObj(this.joiSharedSchemas), null, 2);
|
|
333
|
+
});
|
|
334
|
+
const hashIdx = value.indexOf('#');
|
|
335
|
+
let refSchema;
|
|
336
|
+
|
|
337
|
+
if (hashIdx === -1) {
|
|
338
|
+
// Locate through $ref: 'refId'
|
|
339
|
+
// debug('# Resolving reference through id: %s', value);
|
|
340
|
+
refSchema = this.joiSharedSchemas.get(value);
|
|
341
|
+
} else {
|
|
342
|
+
const id = value.substring(0, hashIdx);
|
|
343
|
+
const path = value.substring(hashIdx + 1);
|
|
344
|
+
|
|
345
|
+
const canonicalURI = id ? value : `${baseUri}${value}`;
|
|
346
|
+
|
|
347
|
+
if (canonicalURI.indexOf('/') === -1) {
|
|
348
|
+
// Locate through $ref: [baseUri]#anchorSchema
|
|
349
|
+
// debug('# Resolving reference through $anchor: %s', canonicalURI);
|
|
350
|
+
refSchema = this.joiSharedSchemas.get(canonicalURI);
|
|
351
|
+
} else if (canonicalURI.indexOf('/properties') === -1) {
|
|
352
|
+
// Locate through $ref: [baseUri]#/$defs/shared
|
|
353
|
+
// debug('# Resolving reference through direct canonicalURI: %s', canonicalURI);
|
|
354
|
+
refSchema = this.joiSharedSchemas.get(canonicalURI);
|
|
355
|
+
} else {
|
|
356
|
+
// Locate through $ref: [baseUri]#/$defs/shared/properties/level1 or [baseUri]#/properties/product
|
|
357
|
+
// Can only get raw schema and build Joi again because it might not completely built yet
|
|
358
|
+
// debug('# Resolving reference through relative path: baseUri - %s; id - %s; path - %s', baseUri, id, path);
|
|
359
|
+
|
|
360
|
+
const paths = path.split('/');
|
|
361
|
+
refSchema = this.subSchemas[id || baseUri];
|
|
362
|
+
|
|
363
|
+
while (paths.length > 0 && refSchema) {
|
|
364
|
+
if (paths[0]) {
|
|
365
|
+
refSchema = refSchema[paths[0]];
|
|
366
|
+
}
|
|
367
|
+
paths.splice(0, 1);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
Hoek.assert(refSchema, `Can not find schema reference: ${value} with baseUri: ${baseUri}`);
|
|
373
|
+
|
|
374
|
+
return refSchema;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
resolveType(schema, resolveOptions, baseUri, isPropertyRequired = false) {
|
|
378
|
+
let joischema;
|
|
379
|
+
|
|
380
|
+
const joitype = (type, options, format) => {
|
|
381
|
+
let joischema;
|
|
382
|
+
|
|
383
|
+
if (options.refineType) {
|
|
384
|
+
type = options.refineType(type, format);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
switch (type) {
|
|
388
|
+
case 'array':
|
|
389
|
+
joischema = this.array(schema, options, baseUri, isPropertyRequired);
|
|
390
|
+
break;
|
|
391
|
+
case 'boolean':
|
|
392
|
+
joischema = this.joi.boolean();
|
|
393
|
+
break;
|
|
394
|
+
case 'integer':
|
|
395
|
+
case 'number':
|
|
396
|
+
joischema = this.number(schema);
|
|
397
|
+
break;
|
|
398
|
+
case 'object':
|
|
399
|
+
joischema = this.object(schema, options, baseUri);
|
|
400
|
+
break;
|
|
401
|
+
case 'string':
|
|
402
|
+
joischema = this.string(schema);
|
|
403
|
+
break;
|
|
404
|
+
case 'null':
|
|
405
|
+
joischema = this.joi.any().valid(...options.customizedNullValues);
|
|
406
|
+
break;
|
|
407
|
+
default:
|
|
408
|
+
joischema = this.joi.types()[type];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
Hoek.assert(joischema, 'Could not resolve type: ' + schema.type + '.');
|
|
412
|
+
|
|
413
|
+
return joischema;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let nullIdx;
|
|
417
|
+
if (Array.isArray(schema.type)) {
|
|
418
|
+
nullIdx = schema.type.findIndex((type) => type === 'null');
|
|
419
|
+
if (nullIdx > -1) {
|
|
420
|
+
schema.type.splice(nullIdx, 1);
|
|
421
|
+
}
|
|
422
|
+
if (schema.type.length === 1) {
|
|
423
|
+
schema.type = schema.type[0];
|
|
424
|
+
} else if (schema.type.length === 0) {
|
|
425
|
+
schema.type = 'null';
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (Array.isArray(schema.type)) {
|
|
430
|
+
const schemas = [];
|
|
431
|
+
|
|
432
|
+
for (let i = 0; i < schema.type.length; i++) {
|
|
433
|
+
schemas.push(joitype(schema.type[i], resolveOptions, schema.format));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
joischema = this.joi.alternatives(schemas);
|
|
437
|
+
} else {
|
|
438
|
+
joischema = joitype(schema.type, resolveOptions, schema.format);
|
|
439
|
+
}
|
|
440
|
+
if (nullIdx > -1) {
|
|
441
|
+
joischema = allowNullIfNeeded(joischema,
|
|
442
|
+
{ allowNull: true, customizedNullValues: resolveOptions.customizedNullValues });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const typeDefinitionMap = {
|
|
446
|
+
title: 'label'
|
|
447
|
+
};
|
|
448
|
+
Object.keys(typeDefinitionMap).forEach(function (key) {
|
|
449
|
+
if (schema[key] !== undefined) {
|
|
450
|
+
joischema = joischema[typeDefinitionMap[key]](schema[key]);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
let desc = schema.description;
|
|
455
|
+
if (resolveOptions.refineDescription) {
|
|
456
|
+
desc = resolveOptions.refineDescription(schema);
|
|
457
|
+
}
|
|
458
|
+
if (desc) {
|
|
459
|
+
joischema = joischema.description(desc);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let example;
|
|
463
|
+
if (schema.examples && schema.examples.length > 0) {
|
|
464
|
+
example = schema.examples[0];
|
|
465
|
+
}
|
|
466
|
+
if (!example && typeof schema.default !== 'undefined') {
|
|
467
|
+
example = schema.default;
|
|
468
|
+
}
|
|
469
|
+
if (!example && schema.enum && schema.enum.length > 0) {
|
|
470
|
+
example = schema.enum[0];
|
|
471
|
+
}
|
|
472
|
+
if (example) {
|
|
473
|
+
joischema = joischema.type === 'array' && !Array.isArray(example) ? joischema.example([example]) : joischema.example(example);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return addEnumRestriction(joischema, schema.enum, resolveOptions);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
copyMetaAnnotations(schema, joischema) {
|
|
480
|
+
const metaDataNames = ['title', 'deprecated', 'readOnly', 'writeOnly'];
|
|
481
|
+
const metas = {};
|
|
482
|
+
for (const metaName of metaDataNames) {
|
|
483
|
+
if (typeof schema[metaName] !== 'undefined') {
|
|
484
|
+
metas[metaName] = schema[metaName];
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (Object.keys(metas).length === 0) {
|
|
489
|
+
return joischema;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return joischema.meta(metas);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
resolveOneOf(schema, resolveOptions, baseUri) {
|
|
496
|
+
Hoek.assert(Array.isArray(schema.oneOf), 'Expected oneOf to be an array.');
|
|
497
|
+
|
|
498
|
+
return this.joi.alternatives(schema.oneOf.map(schema => this.resolve(schema, resolveOptions, baseUri))).match('one');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
resolveAnyOf(schema, resolveOptions, baseUri) {
|
|
502
|
+
Hoek.assert(Array.isArray(schema.anyOf), 'Expected anyOf to be an array.');
|
|
503
|
+
|
|
504
|
+
return this.joi.alternatives(schema.anyOf.map(schema => this.resolve(schema, resolveOptions, baseUri))).match('any');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
resolveAllOf(schema, resolveOptions, baseUri) {
|
|
508
|
+
Hoek.assert(Array.isArray(schema.allOf), 'Expected allOf to be an array.');
|
|
509
|
+
|
|
510
|
+
return this.joi.alternatives(schema.allOf.map(schema => this.resolve(schema, resolveOptions, baseUri))).match('all');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
resolveNot(schema, resolveOptions, baseUri) {
|
|
514
|
+
Hoek.assert(isObject(schema.not), 'Expected Not to be an object.');
|
|
515
|
+
|
|
516
|
+
return this.joi.alternatives().conditional(
|
|
517
|
+
'.',
|
|
518
|
+
{
|
|
519
|
+
not: this.resolve(schema.not, resolveOptions, baseUri),
|
|
520
|
+
then: this.joi.any(),
|
|
521
|
+
otherwise: this.joi.any().forbidden()
|
|
522
|
+
}
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
resolveProperties(jsonSchema, resolveOptions, baseUri) {
|
|
527
|
+
const schemas = {};
|
|
528
|
+
|
|
529
|
+
if (!isObject(jsonSchema.properties)) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
Object.keys(jsonSchema.properties).forEach((key) => {
|
|
534
|
+
const property = jsonSchema.properties[key];
|
|
535
|
+
const isPropertyRequired = Array.isArray(jsonSchema.required) && !!~jsonSchema.required.indexOf(key);
|
|
536
|
+
|
|
537
|
+
const joischema = this.resolve(property, resolveOptions, baseUri, isPropertyRequired);
|
|
538
|
+
|
|
539
|
+
schemas[key] = joischema;
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
return schemas;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
object(schema, resolveOptions, baseUri) {
|
|
546
|
+
let joischema = this.joi.object(this.resolveProperties(schema, resolveOptions, baseUri));
|
|
547
|
+
|
|
548
|
+
if (isObject(schema.additionalProperties)) {
|
|
549
|
+
joischema = joischema.pattern(/^/, this.resolve(schema.additionalProperties, resolveOptions, baseUri));
|
|
550
|
+
} else if (typeof this.joiOptions.allowUnknown === 'undefined' || typeof schema.additionalProperties !== 'undefined') {
|
|
551
|
+
joischema = joischema.unknown(schema.additionalProperties !== false);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
isNumber(schema.minProperties) && (joischema = joischema.min(schema.minProperties));
|
|
555
|
+
isNumber(schema.maxProperties) && (joischema = joischema.max(schema.maxProperties));
|
|
556
|
+
|
|
557
|
+
return joischema;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
resolveAsArray(value, resolveOptions, baseUri, isPropertyRequired = false) {
|
|
561
|
+
return [].concat(value).map((v) => {
|
|
562
|
+
const vSchema = this.resolve(v, resolveOptions, baseUri);
|
|
563
|
+
if (resolveOptions.strictArrayRequired && isPropertyRequired) {
|
|
564
|
+
return vSchema.invalid(...resolveOptions.customizedNullValues);
|
|
565
|
+
}
|
|
566
|
+
return allowNullIfNeeded(vSchema, resolveOptions)
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
array(schema, resolveOptions, baseUri, isPropertyRequired = false) {
|
|
571
|
+
let joischema = this.joi.array();
|
|
572
|
+
let items;
|
|
573
|
+
|
|
574
|
+
if (schema.items) {
|
|
575
|
+
items = this.resolveAsArray(schema.items, resolveOptions, baseUri, isPropertyRequired);
|
|
576
|
+
|
|
577
|
+
joischema = joischema.items(...items);
|
|
578
|
+
} else if (schema.ordered) {
|
|
579
|
+
items = this.resolveAsArray(schema.ordered, resolveOptions, baseUri, isPropertyRequired);
|
|
580
|
+
joischema = joischema.ordered(...items);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (items && schema.additionalItems === false) {
|
|
584
|
+
joischema = joischema.max(items.length);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (isPropertyRequired && resolveOptions.strictArrayRequired && !schema.minItems) {
|
|
588
|
+
schema.minItems = 1;
|
|
589
|
+
}
|
|
590
|
+
isNumber(schema.minItems) && (joischema = joischema.min(schema.minItems));
|
|
591
|
+
isNumber(schema.maxItems) && (joischema = joischema.max(schema.maxItems));
|
|
592
|
+
|
|
593
|
+
if (schema.uniqueItems) {
|
|
594
|
+
joischema = joischema.unique();
|
|
595
|
+
}
|
|
596
|
+
if (schema.contains) {
|
|
597
|
+
joischema = joischema.has(this.resolve(schema.contains, resolveOptions, baseUri));
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return joischema;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
number(schema) {
|
|
604
|
+
let joischema = this.joi.number();
|
|
605
|
+
|
|
606
|
+
if (schema.type === 'integer') {
|
|
607
|
+
joischema = joischema.integer();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
isNumber(schema.minimum) && (joischema = joischema.min(schema.minimum));
|
|
611
|
+
isNumber(schema.maximum) && (joischema = joischema.max(schema.maximum));
|
|
612
|
+
isNumber(schema.exclusiveMinimum) && (joischema = joischema.greater(schema.exclusiveMinimum));
|
|
613
|
+
isNumber(schema.exclusiveMaximum) && (joischema = joischema.less(schema.exclusiveMaximum));
|
|
614
|
+
isNumber(schema.multipleOf) && schema.multipleOf !== 0 && (joischema = joischema.multiple(schema.multipleOf));
|
|
615
|
+
|
|
616
|
+
return joischema;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
string(schema) {
|
|
620
|
+
let joischema = this.joi.string();
|
|
621
|
+
|
|
622
|
+
const dateRegex = '(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])';
|
|
623
|
+
const timeRegex = '([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(.[0-9]+)?(Z|(\\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))';
|
|
624
|
+
const dateTimeRegex = dateRegex + 'T' + timeRegex;
|
|
625
|
+
|
|
626
|
+
switch (schema.format) {
|
|
627
|
+
case 'date':
|
|
628
|
+
return joischema.regex(new RegExp('^' + dateRegex + '$', 'i'), 'JsonSchema date format');
|
|
629
|
+
case 'time':
|
|
630
|
+
return joischema.regex(new RegExp('^' + timeRegex + '$', 'i'), 'JsonSchema time format');
|
|
631
|
+
case 'date-time':
|
|
632
|
+
return joischema.regex(new RegExp('^' + dateTimeRegex + '$', 'i'), 'JsonSchema date-time format');
|
|
633
|
+
case 'binary':
|
|
634
|
+
joischema = this.binary(schema);
|
|
635
|
+
break;
|
|
636
|
+
case 'email':
|
|
637
|
+
return joischema.email();
|
|
638
|
+
case 'hostname':
|
|
639
|
+
return joischema.hostname();
|
|
640
|
+
case 'ipv4':
|
|
641
|
+
return joischema.ip({
|
|
642
|
+
version: ['ipv4']
|
|
643
|
+
});
|
|
644
|
+
case 'ipv6':
|
|
645
|
+
return joischema.ip({
|
|
646
|
+
version: ['ipv6']
|
|
647
|
+
});
|
|
648
|
+
case 'uri':
|
|
649
|
+
return joischema.uri();
|
|
650
|
+
case 'byte':
|
|
651
|
+
joischema = joischema.base64();
|
|
652
|
+
break;
|
|
653
|
+
case 'uuid':
|
|
654
|
+
return joischema.guid({ version: ['uuidv4'] });
|
|
655
|
+
case 'guid':
|
|
656
|
+
return joischema.guid();
|
|
657
|
+
}
|
|
658
|
+
return this.regularString(schema, joischema);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
regularString(schema, joischema) {
|
|
662
|
+
schema.pattern && (joischema = joischema.regex(new RegExp(schema.pattern)));
|
|
663
|
+
|
|
664
|
+
if ((typeof schema.minLength === 'undefined' || schema.minLength === 0) && !schema.pattern && !schema.format && !schema.enum) {
|
|
665
|
+
joischema = joischema.allow('');
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
isNumber(schema.minLength) && (joischema = joischema.min(schema.minLength));
|
|
669
|
+
isNumber(schema.maxLength) && (joischema = joischema.max(schema.maxLength));
|
|
670
|
+
return joischema;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
binary(schema) {
|
|
674
|
+
let joischema = this.joi.binary();
|
|
675
|
+
isNumber(schema.minLength) && (joischema = joischema.min(schema.minLength));
|
|
676
|
+
isNumber(schema.maxLength) && (joischema = joischema.max(schema.maxLength));
|
|
677
|
+
return joischema;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
getValueJoiType(value) {
|
|
681
|
+
let joiType;
|
|
682
|
+
switch (typeof value) {
|
|
683
|
+
case 'string':
|
|
684
|
+
joiType = this.joi.string();
|
|
685
|
+
break;
|
|
686
|
+
case 'number':
|
|
687
|
+
joiType = this.joi.number();
|
|
688
|
+
break;
|
|
689
|
+
case 'boolean':
|
|
690
|
+
joiType = this.joi.boolean();
|
|
691
|
+
break;
|
|
692
|
+
default:
|
|
693
|
+
joiType = this.joi.any();
|
|
694
|
+
}
|
|
695
|
+
return joiType;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
module.exports = SchemaResolver;
|