json-2-joi 2.0.2 → 2.3.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/CHANGELOG.md +14 -0
- package/README.md +69 -3
- package/lib/resolver.js +131 -79
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
### 2.3.0
|
|
2
|
+
|
|
3
|
+
- If `noDefaults` is set `true`, `default()` will not be set to Joi schema and allow it overridable.
|
|
4
|
+
|
|
5
|
+
### 2.2.0
|
|
6
|
+
|
|
7
|
+
- Add `forbidArrayNull`, `strictRequired` option.
|
|
8
|
+
|
|
9
|
+
### 2.1.0
|
|
10
|
+
|
|
11
|
+
- Fix the issue that `enum` is only applied to `string` type or when missing type info.
|
|
12
|
+
- Add `strictEnum` and `enableEnum` option.
|
|
13
|
+
- Allow override options when calling `convert` api.
|
|
14
|
+
|
|
1
15
|
### 2.0.2
|
|
2
16
|
|
|
3
17
|
- Remove `min` setting from JOI schema if `minLength` is not defined in JSON schema.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Converts a JSON schema to a Joi schema for object validation.
|
|
|
6
6
|
|
|
7
7
|
## Change based on the forked version (enjoi):
|
|
8
8
|
|
|
9
|
-
- Provided `refineDescription(schema)`, `allowNull` options
|
|
9
|
+
- Provided `refineDescription(schema)`, `allowNull`, `forbidArrayNull`, `strictRequired`, `strictEnum`, `enableEnum` options
|
|
10
10
|
- Change cycle reference resovling without self-generating id and rely on $id if defined in JSON schema
|
|
11
11
|
- Support `const` keyword in JSON schema
|
|
12
12
|
- Support `example` setting for Joi based on JSON schema's `examples`, `default` or `enum`
|
|
@@ -49,11 +49,16 @@ Please file issues for other unsupported features.
|
|
|
49
49
|
### Options
|
|
50
50
|
|
|
51
51
|
- `subSchemas` - an (optional) object with keys representing schema ids, and values representing schemas.
|
|
52
|
+
- `extensions` - an array of extensions to pass [joi.extend](https://github.com/hapijs/joi/blob/master/API.md#extendextension).
|
|
53
|
+
- `joiOptions` - Options directly passed to Joi for instance creation. If the `noDefaults` is set `true`, `default()` will NOT be called and applied to Joi schema.
|
|
52
54
|
- `refineDescription(schema)` - an (optional) function to call to apply to the JSON schema so that it's possible to return customized description.
|
|
53
55
|
- `refineType(type, format)` - an (optional) function to call to apply to type based on the type and format of the JSON schema.
|
|
54
56
|
- `refineSchema(joiSchema, jsonSchema)` - an (optional) function to call to apply to adjust Joi schema base on the original JSON schema. Primary use case is handling `nullable` flag in OpenAPI 3.0
|
|
55
|
-
- `
|
|
56
|
-
- `
|
|
57
|
+
- `allowNull` - Default as `false`. When `true` and the field has no `enum` setting, `null` value will be allowed.
|
|
58
|
+
- `forbidArrayNull` - Default as `true`. When `false` and `allowNull` is true, `null` value will be allowed for array type field.
|
|
59
|
+
- `strictRequired` - Default as `false`. When `true`, `null` (and empty string additionally for string type) will NOT be allowed for those `required` field in object.
|
|
60
|
+
- `strictEnum` - Default as `true`. When `false`, and `allowNull` is `true`, `null` (and empty string additionally for string type) will be added to `enum` list.
|
|
61
|
+
- `enableEnum` - Default as `true`. When `false`, `enum` restriction will not be added to Joi Schema.
|
|
57
62
|
|
|
58
63
|
Example:
|
|
59
64
|
|
|
@@ -204,6 +209,67 @@ const lengthSchema = json2Joi.convert({
|
|
|
204
209
|
});
|
|
205
210
|
```
|
|
206
211
|
|
|
212
|
+
#### Override options during conversion
|
|
213
|
+
|
|
214
|
+
When reusing the Resolver and calling `convert` API, it's possible that different schema might have different requirement.
|
|
215
|
+
|
|
216
|
+
It's possible to override these options for each conversion:
|
|
217
|
+
|
|
218
|
+
* refineType
|
|
219
|
+
* refineSchema
|
|
220
|
+
* refineDescription
|
|
221
|
+
* allowNull
|
|
222
|
+
* forbidArrayNull
|
|
223
|
+
* strictRequired
|
|
224
|
+
* strictEnum
|
|
225
|
+
* enableEnum
|
|
226
|
+
* noDefaults (Copied from `joiOptions` for the construction time)
|
|
227
|
+
|
|
228
|
+
However, the overriden options do not have impact on the `subSchemas` as they are preprocessed during Resolver construction.
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
const subSchemas = {
|
|
232
|
+
measurement: {
|
|
233
|
+
type: 'object',
|
|
234
|
+
properties: {
|
|
235
|
+
quantity: {
|
|
236
|
+
type: 'number',
|
|
237
|
+
enum: [0, 1]
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
const enjoi = Enjoi.resolver({
|
|
243
|
+
subSchemas,
|
|
244
|
+
enableEnum: false
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const jsonSchema = {
|
|
248
|
+
type: 'object',
|
|
249
|
+
properties: {
|
|
250
|
+
weight: {
|
|
251
|
+
type: 'object',
|
|
252
|
+
properties: {
|
|
253
|
+
quantity: {
|
|
254
|
+
type: 'number',
|
|
255
|
+
enum: [0, 1]
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
length: {
|
|
260
|
+
$ref: 'measurement'
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const schema1 = enjoi.convert(jsonSchema, { enableEnum: true });
|
|
266
|
+
const schema2 = enjoi.convert(jsonSchema);
|
|
267
|
+
|
|
268
|
+
t.ok(schema1.validate({ weight: { quantity: 2 } }).error, 'follow overridden options');
|
|
269
|
+
t.ok(!schema1.validate({ length: { quantity: 2 } }).error, 'no impact on subschemas');
|
|
270
|
+
t.ok(!schema2.validate({ weight: { quantity: 2 } }).error, 'still use original options');
|
|
271
|
+
```
|
|
272
|
+
|
|
207
273
|
### Custom Types
|
|
208
274
|
|
|
209
275
|
Custom types can be provided through the `extensions` option.
|
package/lib/resolver.js
CHANGED
|
@@ -20,9 +20,38 @@ const optionsSchema = Joi.object({
|
|
|
20
20
|
refineSchema: Joi.func().allow(null),
|
|
21
21
|
refineDescription: Joi.func().allow(null),
|
|
22
22
|
allowNull: Joi.boolean().default(false),
|
|
23
|
+
forbidArrayNull: Joi.boolean().default(true),
|
|
24
|
+
strictRequired: Joi.boolean().default(false),
|
|
25
|
+
strictEnum: Joi.boolean().default(true),
|
|
26
|
+
enableEnum: Joi.boolean().default(true),
|
|
23
27
|
joiOptions: Joi.object().unknown(true).default({})
|
|
24
28
|
});
|
|
25
29
|
|
|
30
|
+
const overridableOptions = {
|
|
31
|
+
refineType: Joi.func().allow(null),
|
|
32
|
+
refineSchema: Joi.func().allow(null),
|
|
33
|
+
refineDescription: Joi.func().allow(null),
|
|
34
|
+
allowNull: Joi.boolean(),
|
|
35
|
+
forbidArrayNull: Joi.boolean(),
|
|
36
|
+
strictRequired: Joi.boolean(),
|
|
37
|
+
noDefaults: Joi.boolean(),
|
|
38
|
+
strictEnum: Joi.boolean(),
|
|
39
|
+
enableEnum: Joi.boolean()
|
|
40
|
+
};
|
|
41
|
+
const overridableOptionKeys = Object.keys(overridableOptions);
|
|
42
|
+
const overrideOptionsSchema = Joi.object(overridableOptions);
|
|
43
|
+
|
|
44
|
+
function mergeOverridableOptions(target, ...sources) {
|
|
45
|
+
sources.forEach((src) => {
|
|
46
|
+
overridableOptionKeys.forEach((key) => {
|
|
47
|
+
if (typeof src[key] !== 'undefined') {
|
|
48
|
+
target[key] = src[key];
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
return target;
|
|
53
|
+
}
|
|
54
|
+
|
|
26
55
|
function isNumber(value) {
|
|
27
56
|
return typeof value === 'number';
|
|
28
57
|
}
|
|
@@ -35,13 +64,36 @@ function isNotEmptyObject(value) {
|
|
|
35
64
|
return isObject(value) && Object.keys(value).length > 0;
|
|
36
65
|
}
|
|
37
66
|
|
|
38
|
-
function allowNullIfNeeded(schema, allowNull = false) {
|
|
67
|
+
function allowNullIfNeeded(schema, { allowNull = false, forbidArrayNull = true }) {
|
|
39
68
|
if (allowNull) {
|
|
69
|
+
const definition = schema.describe();
|
|
70
|
+
if (definition.flags && definition.flags.only && definition.allow) {
|
|
71
|
+
// Already has enum restriction
|
|
72
|
+
return schema;
|
|
73
|
+
}
|
|
74
|
+
if (forbidArrayNull && schema.type === 'array') {
|
|
75
|
+
return schema;
|
|
76
|
+
}
|
|
40
77
|
return schema.allow(null);
|
|
41
78
|
}
|
|
42
79
|
return schema;
|
|
43
80
|
}
|
|
44
81
|
|
|
82
|
+
function addEnumRestriction(joiSchema, enumList, { allowNull = false, strictEnum = true, enableEnum = true }) {
|
|
83
|
+
if (!enableEnum || !Array.isArray(enumList) || enumList.length === 0) {
|
|
84
|
+
return joiSchema;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const valids = [].concat(enumList);
|
|
88
|
+
if (allowNull && !strictEnum) {
|
|
89
|
+
valids.splice(0, 0, null);
|
|
90
|
+
if (joiSchema.type === 'string') {
|
|
91
|
+
valids.splice(0, 0, '');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return joiSchema.valid(...valids);
|
|
95
|
+
}
|
|
96
|
+
|
|
45
97
|
function normalizedId(id) {
|
|
46
98
|
return id.replace(/[^a-zA-Z0-9]/g, '_');
|
|
47
99
|
}
|
|
@@ -104,15 +156,13 @@ class SchemaResolver {
|
|
|
104
156
|
|
|
105
157
|
Hoek.assert(!validatedResult.error, validatedResult.error);
|
|
106
158
|
|
|
107
|
-
const { subSchemas = {},
|
|
159
|
+
const { subSchemas = {}, extensions = [], joiOptions } = validatedResult.value;
|
|
108
160
|
|
|
161
|
+
this.resolveOptions = mergeOverridableOptions({}, validatedResult.value);
|
|
109
162
|
this.subSchemas = subSchemas;
|
|
110
|
-
this.refineType = refineType;
|
|
111
|
-
this.refineDescription = refineDescription;
|
|
112
|
-
this.refineSchema = refineSchema;
|
|
113
|
-
this.allowNull = allowNull;
|
|
114
163
|
this.joiOptions = joiOptions;
|
|
115
164
|
this.joiSharedSchemas = new Map();
|
|
165
|
+
this.resolveOptions.noDefaults = this.joiOptions.noDefaults;
|
|
116
166
|
|
|
117
167
|
this.joi = getJoiInstance(joiOptions, extensions, joiInstance);
|
|
118
168
|
|
|
@@ -122,7 +172,7 @@ class SchemaResolver {
|
|
|
122
172
|
// shorthand schema type does not need to set $id
|
|
123
173
|
sharedSchema.$id = sharedSchemaId;
|
|
124
174
|
}
|
|
125
|
-
this.joiSharedSchemas.set(sharedSchemaId, this.resolve(sharedSchema));
|
|
175
|
+
this.joiSharedSchemas.set(sharedSchemaId, this.resolve(sharedSchema, this.resolveOptions));
|
|
126
176
|
}
|
|
127
177
|
|
|
128
178
|
const itr = this.joiSharedSchemas.keys();
|
|
@@ -132,26 +182,29 @@ class SchemaResolver {
|
|
|
132
182
|
}
|
|
133
183
|
}
|
|
134
184
|
|
|
135
|
-
resolveShareSchemas(schema,
|
|
185
|
+
resolveShareSchemas(schema, resolveOptions, sharedProperties = '$defs', baseUri = '') {
|
|
136
186
|
const localShared = schema[sharedProperties]
|
|
137
187
|
if (localShared) {
|
|
138
188
|
for (let sharedSchemaId in localShared) {
|
|
139
189
|
this.joiSharedSchemas.set(
|
|
140
190
|
`${baseUri}#/${sharedProperties}/${sharedSchemaId}`,
|
|
141
|
-
this.resolve(localShared[sharedSchemaId], baseUri)
|
|
191
|
+
this.resolve(localShared[sharedSchemaId], resolveOptions, baseUri)
|
|
142
192
|
);
|
|
143
193
|
}
|
|
144
194
|
}
|
|
145
195
|
}
|
|
146
196
|
|
|
147
|
-
convert(schema) {
|
|
197
|
+
convert(schema, options = {}) {
|
|
148
198
|
const validateSchema = schemaSchema.validate(schema);
|
|
199
|
+
const { value: overrideOptions, error: overrideOptionsError } = overrideOptionsSchema.validate(options);
|
|
149
200
|
|
|
150
201
|
Hoek.assert(!validateSchema.error, validateSchema.error);
|
|
202
|
+
Hoek.assert(!overrideOptionsError, overrideOptionsError);
|
|
151
203
|
|
|
152
204
|
const schemaId = schema.$id || '';
|
|
153
205
|
this.subSchemas[schemaId] = schema;
|
|
154
|
-
|
|
206
|
+
|
|
207
|
+
const joiSchema = this.resolve(schema, mergeOverridableOptions({}, this.resolveOptions, overrideOptions));
|
|
155
208
|
|
|
156
209
|
// Cleanup all shared schema registered other than the initial shared ones
|
|
157
210
|
delete this.subSchemas[schemaId];
|
|
@@ -165,7 +218,7 @@ class SchemaResolver {
|
|
|
165
218
|
return joiSchema;
|
|
166
219
|
}
|
|
167
220
|
|
|
168
|
-
resolve(schema, baseUri = (schema.$id || '')) {
|
|
221
|
+
resolve(schema, resolveOptions = {}, baseUri = (schema.$id || '')) {
|
|
169
222
|
if (this.joi.isSchema(schema)) {
|
|
170
223
|
return schema;
|
|
171
224
|
}
|
|
@@ -180,46 +233,48 @@ class SchemaResolver {
|
|
|
180
233
|
}
|
|
181
234
|
}
|
|
182
235
|
|
|
183
|
-
this.resolveShareSchemas(schema,
|
|
184
|
-
this.resolveShareSchemas(schema,
|
|
236
|
+
this.resolveShareSchemas(schema, resolveOptions, '$defs', baseUri);
|
|
237
|
+
this.resolveShareSchemas(schema, resolveOptions, 'definitions', baseUri);
|
|
185
238
|
|
|
186
239
|
if (typeof schema === 'string') {
|
|
187
240
|
// If schema is itself a string, interpret it as a type
|
|
188
|
-
resolvedSchema = this.resolveType({ type: schema });
|
|
241
|
+
resolvedSchema = this.resolveType({ type: schema }, resolveOptions, baseUri);
|
|
189
242
|
} else if (schema.$ref) {
|
|
190
243
|
if (schema.$ref.trim() === '#') {
|
|
191
244
|
return baseUri ? this.joi.link(`#${normalizedId(baseUri)}`) : this.joi.link('/');
|
|
192
245
|
}
|
|
193
246
|
|
|
194
|
-
resolvedSchema = this.resolve(this.resolveReference(schema.$ref, baseUri), baseUri);
|
|
247
|
+
resolvedSchema = this.resolve(this.resolveReference(schema.$ref, baseUri), resolveOptions, baseUri);
|
|
195
248
|
} else {
|
|
196
249
|
const partialSchemas = [];
|
|
197
250
|
if (schema.type) {
|
|
198
|
-
partialSchemas.push(this.resolveType(schema, baseUri));
|
|
251
|
+
partialSchemas.push(this.resolveType(schema, resolveOptions, baseUri));
|
|
199
252
|
} else if (schema.properties) {
|
|
200
253
|
// if no type is specified, just properties
|
|
201
|
-
partialSchemas.push(this.object(schema, baseUri))
|
|
254
|
+
partialSchemas.push(this.object(schema, resolveOptions, baseUri))
|
|
202
255
|
} else if (schema.format) {
|
|
203
256
|
// if no type is specified, just format
|
|
204
257
|
partialSchemas.push(this.string(schema))
|
|
205
258
|
} else if (schema.enum) {
|
|
206
259
|
// If no type is specified, just enum
|
|
207
|
-
partialSchemas.push(
|
|
260
|
+
partialSchemas.push(
|
|
261
|
+
addEnumRestriction(this.joi.any().example(schema.enum[0]), schema.enum, resolveOptions)
|
|
262
|
+
);
|
|
208
263
|
} else if (schema.const) {
|
|
209
264
|
// Constant value
|
|
210
265
|
partialSchemas.push(this.getValueJoiType(schema.const).valid(schema.const));
|
|
211
266
|
}
|
|
212
267
|
if (schema.anyOf) {
|
|
213
|
-
partialSchemas.push(this.resolveAnyOf(schema, baseUri));
|
|
268
|
+
partialSchemas.push(this.resolveAnyOf(schema, resolveOptions, baseUri));
|
|
214
269
|
}
|
|
215
270
|
if (schema.allOf) {
|
|
216
|
-
partialSchemas.push(this.resolveAllOf(schema, baseUri));
|
|
271
|
+
partialSchemas.push(this.resolveAllOf(schema, resolveOptions, baseUri));
|
|
217
272
|
}
|
|
218
273
|
if (schema.oneOf) {
|
|
219
|
-
partialSchemas.push(this.resolveOneOf(schema, baseUri));
|
|
274
|
+
partialSchemas.push(this.resolveOneOf(schema, resolveOptions, baseUri));
|
|
220
275
|
}
|
|
221
276
|
if (schema.not) {
|
|
222
|
-
partialSchemas.push(this.resolveNot(schema, baseUri));
|
|
277
|
+
partialSchemas.push(this.resolveNot(schema, resolveOptions, baseUri));
|
|
223
278
|
}
|
|
224
279
|
if (partialSchemas.length === 0) {
|
|
225
280
|
//Fall through to whatever.
|
|
@@ -231,11 +286,11 @@ class SchemaResolver {
|
|
|
231
286
|
resolvedSchema = partialSchemas.length === 1 ? partialSchemas[0] : this.joi.alternatives(partialSchemas).match('all');
|
|
232
287
|
}
|
|
233
288
|
|
|
234
|
-
if (
|
|
235
|
-
resolvedSchema =
|
|
289
|
+
if (resolveOptions.refineSchema) {
|
|
290
|
+
resolvedSchema = resolveOptions.refineSchema(resolvedSchema, schema);
|
|
236
291
|
}
|
|
237
292
|
|
|
238
|
-
if (schema.default !== undefined) {
|
|
293
|
+
if (schema.default !== undefined && !resolveOptions.noDefaults) {
|
|
239
294
|
resolvedSchema = resolvedSchema.default(schema.default)
|
|
240
295
|
}
|
|
241
296
|
|
|
@@ -299,24 +354,23 @@ class SchemaResolver {
|
|
|
299
354
|
return refSchema;
|
|
300
355
|
}
|
|
301
356
|
|
|
302
|
-
resolveType(schema, baseUri) {
|
|
357
|
+
resolveType(schema, resolveOptions, baseUri) {
|
|
303
358
|
let joischema;
|
|
304
359
|
|
|
305
360
|
const typeDefinitionMap = {
|
|
306
|
-
title: 'label'
|
|
307
|
-
default: 'default'
|
|
361
|
+
title: 'label'
|
|
308
362
|
};
|
|
309
363
|
|
|
310
|
-
const joitype = (type, format) => {
|
|
364
|
+
const joitype = (type, options, format) => {
|
|
311
365
|
let joischema;
|
|
312
366
|
|
|
313
|
-
if (
|
|
314
|
-
type =
|
|
367
|
+
if (options.refineType) {
|
|
368
|
+
type = options.refineType(type, format);
|
|
315
369
|
}
|
|
316
370
|
|
|
317
371
|
switch (type) {
|
|
318
372
|
case 'array':
|
|
319
|
-
joischema = this.array(schema, baseUri);
|
|
373
|
+
joischema = this.array(schema, options, baseUri);
|
|
320
374
|
break;
|
|
321
375
|
case 'boolean':
|
|
322
376
|
joischema = this.joi.boolean();
|
|
@@ -326,7 +380,7 @@ class SchemaResolver {
|
|
|
326
380
|
joischema = this.number(schema);
|
|
327
381
|
break;
|
|
328
382
|
case 'object':
|
|
329
|
-
joischema = this.object(schema, baseUri);
|
|
383
|
+
joischema = this.object(schema, options, baseUri);
|
|
330
384
|
break;
|
|
331
385
|
case 'string':
|
|
332
386
|
joischema = this.string(schema);
|
|
@@ -347,12 +401,12 @@ class SchemaResolver {
|
|
|
347
401
|
const schemas = [];
|
|
348
402
|
|
|
349
403
|
for (let i = 0; i < schema.type.length; i++) {
|
|
350
|
-
schemas.push(joitype(schema.type[i], schema.format));
|
|
404
|
+
schemas.push(joitype(schema.type[i], resolveOptions, schema.format));
|
|
351
405
|
}
|
|
352
406
|
|
|
353
407
|
joischema = this.joi.alternatives(schemas);
|
|
354
408
|
} else {
|
|
355
|
-
joischema = joitype(schema.type, schema.format);
|
|
409
|
+
joischema = joitype(schema.type, resolveOptions, schema.format);
|
|
356
410
|
}
|
|
357
411
|
|
|
358
412
|
Object.keys(typeDefinitionMap).forEach(function (key) {
|
|
@@ -362,8 +416,8 @@ class SchemaResolver {
|
|
|
362
416
|
});
|
|
363
417
|
|
|
364
418
|
let desc = schema.description;
|
|
365
|
-
if (
|
|
366
|
-
desc =
|
|
419
|
+
if (resolveOptions.refineDescription) {
|
|
420
|
+
desc = resolveOptions.refineDescription(schema);
|
|
367
421
|
}
|
|
368
422
|
if (desc) {
|
|
369
423
|
joischema = joischema.description(desc);
|
|
@@ -383,70 +437,72 @@ class SchemaResolver {
|
|
|
383
437
|
joischema = joischema.type === 'array' && !Array.isArray(example) ? joischema.example([example]) : joischema.example(example);
|
|
384
438
|
}
|
|
385
439
|
|
|
386
|
-
return joischema;
|
|
440
|
+
return addEnumRestriction(joischema, schema.enum, resolveOptions);
|
|
387
441
|
}
|
|
388
442
|
|
|
389
|
-
resolveOneOf(schema, baseUri) {
|
|
443
|
+
resolveOneOf(schema, resolveOptions, baseUri) {
|
|
390
444
|
Hoek.assert(Array.isArray(schema.oneOf), 'Expected oneOf to be an array.');
|
|
391
445
|
|
|
392
|
-
return this.joi.alternatives(schema.oneOf.map(schema => this.resolve(schema, baseUri))).match('one');
|
|
446
|
+
return this.joi.alternatives(schema.oneOf.map(schema => this.resolve(schema, resolveOptions, baseUri))).match('one');
|
|
393
447
|
}
|
|
394
448
|
|
|
395
|
-
resolveAnyOf(schema, baseUri) {
|
|
449
|
+
resolveAnyOf(schema, resolveOptions, baseUri) {
|
|
396
450
|
Hoek.assert(Array.isArray(schema.anyOf), 'Expected anyOf to be an array.');
|
|
397
451
|
|
|
398
|
-
return this.joi.alternatives(schema.anyOf.map(schema => this.resolve(schema, baseUri))).match('any');
|
|
452
|
+
return this.joi.alternatives(schema.anyOf.map(schema => this.resolve(schema, resolveOptions, baseUri))).match('any');
|
|
399
453
|
}
|
|
400
454
|
|
|
401
|
-
resolveAllOf(schema, baseUri) {
|
|
455
|
+
resolveAllOf(schema, resolveOptions, baseUri) {
|
|
402
456
|
Hoek.assert(Array.isArray(schema.allOf), 'Expected allOf to be an array.');
|
|
403
457
|
|
|
404
|
-
return this.joi.alternatives(schema.allOf.map(schema => this.resolve(schema, baseUri))).match('all');
|
|
458
|
+
return this.joi.alternatives(schema.allOf.map(schema => this.resolve(schema, resolveOptions, baseUri))).match('all');
|
|
405
459
|
}
|
|
406
460
|
|
|
407
|
-
resolveNot(schema, baseUri) {
|
|
461
|
+
resolveNot(schema, resolveOptions, baseUri) {
|
|
408
462
|
Hoek.assert(isObject(schema.not), 'Expected Not to be an object.');
|
|
409
463
|
|
|
410
464
|
return this.joi.alternatives().conditional(
|
|
411
465
|
'.',
|
|
412
466
|
{
|
|
413
|
-
not: this.resolve(schema.not, baseUri),
|
|
467
|
+
not: this.resolve(schema.not, resolveOptions, baseUri),
|
|
414
468
|
then: this.joi.any(),
|
|
415
469
|
otherwise: this.joi.any().forbidden()
|
|
416
470
|
}
|
|
417
471
|
);
|
|
418
472
|
}
|
|
419
473
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const resolveproperties = () => {
|
|
423
|
-
const schemas = {};
|
|
474
|
+
resolveProperties(jsonSchema, resolveOptions, baseUri) {
|
|
475
|
+
const schemas = {};
|
|
424
476
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
477
|
+
if (!isObject(jsonSchema.properties)) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
428
480
|
|
|
429
|
-
|
|
430
|
-
|
|
481
|
+
Object.keys(jsonSchema.properties).forEach((key) => {
|
|
482
|
+
const property = jsonSchema.properties[key];
|
|
431
483
|
|
|
432
|
-
|
|
484
|
+
let joischema = this.resolve(property, resolveOptions, baseUri);
|
|
433
485
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
joischema =
|
|
486
|
+
if (Array.isArray(jsonSchema.required) && !!~jsonSchema.required.indexOf(key)) {
|
|
487
|
+
joischema = joischema.required();
|
|
488
|
+
if (resolveOptions.strictRequired) {
|
|
489
|
+
joischema = joischema.invalid(null, '');
|
|
438
490
|
}
|
|
491
|
+
} else {
|
|
492
|
+
joischema = allowNullIfNeeded(joischema, resolveOptions);
|
|
493
|
+
}
|
|
439
494
|
|
|
440
|
-
|
|
441
|
-
|
|
495
|
+
schemas[key] = joischema;
|
|
496
|
+
});
|
|
442
497
|
|
|
443
|
-
|
|
444
|
-
|
|
498
|
+
return schemas;
|
|
499
|
+
}
|
|
445
500
|
|
|
446
|
-
|
|
501
|
+
object(schema, resolveOptions, baseUri) {
|
|
502
|
+
let joischema = this.joi.object(this.resolveProperties(schema, resolveOptions, baseUri));
|
|
447
503
|
|
|
448
504
|
if (isObject(schema.additionalProperties)) {
|
|
449
|
-
joischema = joischema.pattern(/^/, this.resolve(schema.additionalProperties, baseUri));
|
|
505
|
+
joischema = joischema.pattern(/^/, this.resolve(schema.additionalProperties, resolveOptions, baseUri));
|
|
450
506
|
} else if (typeof this.joiOptions.allowUnknown === 'undefined') {
|
|
451
507
|
joischema = joischema.unknown(schema.additionalProperties !== false);
|
|
452
508
|
}
|
|
@@ -457,25 +513,25 @@ class SchemaResolver {
|
|
|
457
513
|
return joischema;
|
|
458
514
|
}
|
|
459
515
|
|
|
460
|
-
resolveAsArray(value, baseUri) {
|
|
516
|
+
resolveAsArray(value, resolveOptions, baseUri) {
|
|
461
517
|
if (Array.isArray(value)) {
|
|
462
518
|
// found an array, thus its _per type_
|
|
463
|
-
return value.map((v) => allowNullIfNeeded(this.resolve(v, baseUri),
|
|
519
|
+
return value.map((v) => allowNullIfNeeded(this.resolve(v, resolveOptions, baseUri), resolveOptions));
|
|
464
520
|
}
|
|
465
521
|
// it's a single entity, so just resolve it normally
|
|
466
|
-
return [allowNullIfNeeded(this.resolve(value, baseUri),
|
|
522
|
+
return [allowNullIfNeeded(this.resolve(value, resolveOptions, baseUri), resolveOptions)];
|
|
467
523
|
}
|
|
468
524
|
|
|
469
|
-
array(schema, baseUri) {
|
|
525
|
+
array(schema, resolveOptions, baseUri) {
|
|
470
526
|
let joischema = this.joi.array();
|
|
471
527
|
let items;
|
|
472
528
|
|
|
473
529
|
if (schema.items) {
|
|
474
|
-
items = this.resolveAsArray(schema.items, baseUri);
|
|
530
|
+
items = this.resolveAsArray(schema.items, resolveOptions, baseUri);
|
|
475
531
|
|
|
476
532
|
joischema = joischema.items(...items);
|
|
477
533
|
} else if (schema.ordered) {
|
|
478
|
-
items = this.resolveAsArray(schema.ordered, baseUri);
|
|
534
|
+
items = this.resolveAsArray(schema.ordered, resolveOptions, baseUri);
|
|
479
535
|
joischema = joischema.ordered(...items);
|
|
480
536
|
}
|
|
481
537
|
|
|
@@ -490,7 +546,7 @@ class SchemaResolver {
|
|
|
490
546
|
joischema = joischema.unique();
|
|
491
547
|
}
|
|
492
548
|
if (schema.contains) {
|
|
493
|
-
joischema = joischema.has(this.resolve(schema.contains, baseUri));
|
|
549
|
+
joischema = joischema.has(this.resolve(schema.contains, resolveOptions, baseUri));
|
|
494
550
|
}
|
|
495
551
|
|
|
496
552
|
return joischema;
|
|
@@ -519,10 +575,6 @@ class SchemaResolver {
|
|
|
519
575
|
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]))';
|
|
520
576
|
const dateTimeRegex = dateRegex + 'T' + timeRegex;
|
|
521
577
|
|
|
522
|
-
if (schema.enum) {
|
|
523
|
-
return this.joi.string().valid(...schema.enum);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
578
|
switch (schema.format) {
|
|
527
579
|
case 'date':
|
|
528
580
|
return joischema.regex(new RegExp('^' + dateRegex + '$', 'i'), 'JsonSchema date format');
|
|
@@ -561,7 +613,7 @@ class SchemaResolver {
|
|
|
561
613
|
regularString(schema, joischema) {
|
|
562
614
|
schema.pattern && (joischema = joischema.regex(new RegExp(schema.pattern)));
|
|
563
615
|
|
|
564
|
-
if ((typeof schema.minLength === 'undefined' || schema.minLength === 0) && !schema.pattern && !schema.format) {
|
|
616
|
+
if ((typeof schema.minLength === 'undefined' || schema.minLength === 0) && !schema.pattern && !schema.format && !schema.enum) {
|
|
565
617
|
joischema = joischema.allow('');
|
|
566
618
|
}
|
|
567
619
|
|