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