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