json-2-joi 1.0.0 → 2.0.1
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 +34 -0
- package/README.md +21 -8
- package/index.js +4 -4
- package/lib/resolver.js +58 -44
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
### 2.0.1
|
|
2
|
+
|
|
3
|
+
- Set correct examples data for Array type field.
|
|
4
|
+
|
|
5
|
+
### 2.0.0
|
|
6
|
+
|
|
7
|
+
* [BREAKING] Remove `useDefaults` and `strictMode` options
|
|
8
|
+
|
|
9
|
+
### 1.1.0
|
|
10
|
+
|
|
11
|
+
- Support providing `joiOptions` for JOI default instance creation.
|
|
12
|
+
- Support directly passing a Joi instance for schema building.
|
|
13
|
+
|
|
14
|
+
### 1.0.0
|
|
15
|
+
|
|
16
|
+
- Provided `refineDescription(schema)`, `allowNull` options
|
|
17
|
+
- Change cycle reference resovling without self-generating id and rely on $id if defined in JSON schema
|
|
18
|
+
- Support `const` keyword in JSON schema
|
|
19
|
+
- Support `example` setting for Joi based on JSON schema's `examples`, `default` or `enum`
|
|
20
|
+
- Support `contains` keyword in JSON schema using Joi's `has` method
|
|
21
|
+
- Support `$anchor` keyword in JSON schema
|
|
22
|
+
- Remove `defaults` API. Please refer to section **Reuse JOI Schema Resolver** for similar usage.
|
|
23
|
+
- **Big Change** on `$ref` and `subSchemas`. Please refer to [test/test-references.js](test/test-references.js) for detail usage. Currently, four format of `$ref` are supported:
|
|
24
|
+
* id
|
|
25
|
+
* [baseUri]#anchor
|
|
26
|
+
* [baseUri]#/$defs/shared
|
|
27
|
+
* [baseUri]#/$defs/shared/properties/level1
|
|
28
|
+
|
|
29
|
+
**subSchemas will be modified** for two reason:
|
|
30
|
+
* If any subSchema does not have `$id` field, it will be added using the corresponding key in `subSchemas` object.
|
|
31
|
+
* New key-value pair will be added into it if the schema or any its subschema being parsed has `$id` field.
|
|
32
|
+
|
|
33
|
+
## Before Fork
|
|
34
|
+
|
|
1
35
|
### 9.0.1
|
|
2
36
|
|
|
3
37
|
* #112 Use joi.link to only resolve strictly recursive self-references
|
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
 [](https://badge.fury.io/js/json-2-joi)
|
|
2
2
|
|
|
3
3
|
# json-2-joi
|
|
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
9
|
- Provided `refineDescription(schema)`, `allowNull` options
|
|
10
10
|
- Change cycle reference resovling without self-generating id and rely on $id if defined in JSON schema
|
|
@@ -12,21 +12,25 @@ Converts a JSON schema to a Joi schema for object validation.
|
|
|
12
12
|
- Support `example` setting for Joi based on JSON schema's `examples`, `default` or `enum`
|
|
13
13
|
- Support `contains` keyword in JSON schema using Joi's `has` method
|
|
14
14
|
- Support `$anchor` keyword in JSON schema
|
|
15
|
+
- Support providing `joiOptions` for JOI default instance creation.
|
|
16
|
+
- Support directly passing a Joi instance for schema building.
|
|
17
|
+
- Remove `defaults` API. Please refer to section **Reuse JOI Schema Resolver** for similar usage.
|
|
18
|
+
- Remove `strictMode` and `useDefaults` option.
|
|
15
19
|
- **Big Change** on `$ref` and `subSchemas`. Please refer to [test/test-references.js](test/test-references.js) for detail usage. Currently, four format of `$ref` are supported:
|
|
16
20
|
* id
|
|
17
21
|
* [baseUri]#anchor
|
|
18
22
|
* [baseUri]#/$defs/shared
|
|
19
23
|
* [baseUri]#/$defs/shared/properties/level1
|
|
20
24
|
|
|
21
|
-
**subSchemas
|
|
25
|
+
**subSchemas will be modified** for two reason:
|
|
22
26
|
* If any subSchema does not have `$id` field, it will be added using the corresponding key in `subSchemas` object.
|
|
23
|
-
* New key-value pair will be added into it if the
|
|
27
|
+
* New key-value pair will be added into it if the schema or any its subschema being parsed has `$id` field.
|
|
24
28
|
|
|
25
29
|
## Usage
|
|
26
30
|
|
|
27
31
|
### Schema Support
|
|
28
32
|
|
|
29
|
-
`
|
|
33
|
+
`json-2-joi` does not support all of json-schema.
|
|
30
34
|
|
|
31
35
|
Here is a list of some known missing keyword support still being worked on:
|
|
32
36
|
|
|
@@ -36,10 +40,11 @@ Please file issues for other unsupported features.
|
|
|
36
40
|
|
|
37
41
|
### API
|
|
38
42
|
|
|
39
|
-
- `json2Joi.schema(schema [, options])`
|
|
43
|
+
- `json2Joi.schema(schema [, options, joiInstance])`
|
|
40
44
|
- `schema` - a JSON schema or a string type representation (such as `'integer'`).
|
|
41
45
|
- `options` - an (optional) object of additional options such as `subSchemas` and custom `types`.
|
|
42
|
-
- `
|
|
46
|
+
- `joiInstance` - a (optional) Joi instance to be used. When passed, it will be used directly. `extensions` option does not take effect and the Object/Array auto coerce extensions will not be included as well.
|
|
47
|
+
- `json2Joi.resolver(options [, joiInstance])` - Creates a schema resolver based on `options`. `joiInstance` is same as above.
|
|
43
48
|
|
|
44
49
|
### Options
|
|
45
50
|
|
|
@@ -49,7 +54,6 @@ Please file issues for other unsupported features.
|
|
|
49
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
|
|
50
55
|
- `extensions` - an array of extensions to pass [joi.extend](https://github.com/hapijs/joi/blob/master/API.md#extendextension).
|
|
51
56
|
- `allowNull` - Allows null value when the field is NOT required with a default value of `false`.
|
|
52
|
-
- `strictMode` - make schemas `strict(value)` with a default value of `false`.
|
|
53
57
|
|
|
54
58
|
Example:
|
|
55
59
|
|
|
@@ -79,6 +83,15 @@ const schema = Json2Joi.schema({
|
|
|
79
83
|
|
|
80
84
|
const { error, value } = schema.validate({firstName: 'John', lastName: 'Doe', age: 45});
|
|
81
85
|
```
|
|
86
|
+
schema.additionalProperties !== false
|
|
87
|
+
### joiOptions
|
|
88
|
+
|
|
89
|
+
When this option is provided, a Joi instance will be created as `joiInstance = Joi.defaults((schema) => schema.options(joiOptions));` for building Joi Schema.
|
|
90
|
+
|
|
91
|
+
As a result:
|
|
92
|
+
* `unknown` setting (originally set by schema.additionalProperties !== false) of the schema will take effect only if `allowUnknown` is NOT provided in `joiOptions`.
|
|
93
|
+
* Original `useDefaults` option will be replaced by `noDefaults` in `joiOptions`.
|
|
94
|
+
* Original `strictMode` option will be replaced by `convert` in `joiOptions`.
|
|
82
95
|
|
|
83
96
|
### Sub Schemas
|
|
84
97
|
|
package/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const SchemaResolver = require('./lib/resolver');
|
|
2
2
|
|
|
3
|
-
function resolver(options = {}) {
|
|
4
|
-
return new SchemaResolver(options);
|
|
3
|
+
function resolver(options = {}, joiInstance) {
|
|
4
|
+
return new SchemaResolver(options, joiInstance);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
function schema(schema, options = { }) {
|
|
8
|
-
const joiResolver = resolver(options);
|
|
7
|
+
function schema(schema, options = { }, joiInstance) {
|
|
8
|
+
const joiResolver = resolver(options, joiInstance);
|
|
9
9
|
const joiSchema = joiResolver.convert(schema);
|
|
10
10
|
return joiSchema;
|
|
11
11
|
}
|
package/lib/resolver.js
CHANGED
|
@@ -20,8 +20,7 @@ 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
|
-
|
|
24
|
-
useDefaults: Joi.boolean().default(false)
|
|
23
|
+
joiOptions: Joi.object().unknown(true).default({})
|
|
25
24
|
});
|
|
26
25
|
|
|
27
26
|
function isNumber(value) {
|
|
@@ -32,10 +31,59 @@ function isObject(value) {
|
|
|
32
31
|
return value !== null && typeof value === 'object';
|
|
33
32
|
}
|
|
34
33
|
|
|
34
|
+
function isNotEmptyObject(value) {
|
|
35
|
+
return isObject(value) && Object.keys(value).length > 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
35
38
|
function normalizedId(id) {
|
|
36
39
|
return id.replace(/[^a-zA-Z0-9]/g, '_');
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
function getJoiInstance(joiOptions, extensions, joiInstance) {
|
|
43
|
+
if (joiInstance) {
|
|
44
|
+
return joiInstance;
|
|
45
|
+
}
|
|
46
|
+
if (isNotEmptyObject(joiOptions)) {
|
|
47
|
+
joiInstance = Joi.defaults((schema) => schema.options(joiOptions));
|
|
48
|
+
} else {
|
|
49
|
+
joiInstance = Joi;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return joiInstance.extend(
|
|
53
|
+
{
|
|
54
|
+
type: 'object',
|
|
55
|
+
base: joiInstance.object(),
|
|
56
|
+
coerce: {
|
|
57
|
+
from: 'string',
|
|
58
|
+
method(value) {
|
|
59
|
+
if (typeof value !== 'string' || (value[0] !== '{' && !/^\s*\{/.test(value))) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return { value: Bourne.parse(value) };
|
|
64
|
+
} catch (ignoreErr) { } // eslint-disable-line no-empty
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'array',
|
|
70
|
+
base: joiInstance.array(),
|
|
71
|
+
coerce: {
|
|
72
|
+
from: 'string',
|
|
73
|
+
method(value) {
|
|
74
|
+
if (typeof value !== 'string' || (value[0] !== '[' && !/^\s*\[/.test(value))) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
return { value: Bourne.parse(value) };
|
|
79
|
+
} catch (ignoreErr) { } // eslint-disable-line no-empty
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
...extensions
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
39
87
|
const mapToObj = m => {
|
|
40
88
|
return Array.from(m).reduce((obj, [key, value]) => {
|
|
41
89
|
obj[key] = Joi.isSchema(value) ? value.describe() : value;
|
|
@@ -44,56 +92,22 @@ const mapToObj = m => {
|
|
|
44
92
|
};
|
|
45
93
|
|
|
46
94
|
class SchemaResolver {
|
|
47
|
-
constructor(options) {
|
|
95
|
+
constructor(options, joiInstance) {
|
|
48
96
|
const validatedResult = optionsSchema.validate(options);
|
|
49
97
|
|
|
50
98
|
Hoek.assert(!validatedResult.error, validatedResult.error);
|
|
51
99
|
|
|
52
|
-
const { subSchemas = {}, refineType, refineSchema,
|
|
100
|
+
const { subSchemas = {}, refineType, refineSchema, extensions = [], allowNull = false, refineDescription, joiOptions } = validatedResult.value;
|
|
53
101
|
|
|
54
102
|
this.subSchemas = subSchemas;
|
|
55
103
|
this.refineType = refineType;
|
|
56
104
|
this.refineDescription = refineDescription;
|
|
57
105
|
this.refineSchema = refineSchema;
|
|
58
|
-
this.strictMode = strictMode;
|
|
59
|
-
this.useDefaults = useDefaults;
|
|
60
106
|
this.allowNull = allowNull;
|
|
107
|
+
this.joiOptions = joiOptions;
|
|
61
108
|
this.joiSharedSchemas = new Map();
|
|
62
109
|
|
|
63
|
-
this.joi =
|
|
64
|
-
{
|
|
65
|
-
type: 'object',
|
|
66
|
-
base: Joi.object(),
|
|
67
|
-
coerce: {
|
|
68
|
-
from: 'string',
|
|
69
|
-
method(value) {
|
|
70
|
-
if (typeof value !== 'string' || (value[0] !== '{' && !/^\s*\{/.test(value))) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
return { value: Bourne.parse(value) };
|
|
76
|
-
} catch (ignoreErr) { } // eslint-disable-line no-empty
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
type: 'array',
|
|
82
|
-
base: Joi.array(),
|
|
83
|
-
coerce: {
|
|
84
|
-
from: 'string',
|
|
85
|
-
method(value) {
|
|
86
|
-
if (typeof value !== 'string' || (value[0] !== '[' && !/^\s*\[/.test(value))) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
return { value: Bourne.parse(value) };
|
|
91
|
-
} catch (ignoreErr) { } // eslint-disable-line no-empty
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
...extensions
|
|
96
|
-
);
|
|
110
|
+
this.joi = getJoiInstance(joiOptions, extensions, joiInstance);
|
|
97
111
|
|
|
98
112
|
for (let sharedSchemaId in this.subSchemas) {
|
|
99
113
|
const sharedSchema = this.subSchemas[sharedSchemaId];
|
|
@@ -214,7 +228,7 @@ class SchemaResolver {
|
|
|
214
228
|
resolvedSchema = this.refineSchema(resolvedSchema, schema);
|
|
215
229
|
}
|
|
216
230
|
|
|
217
|
-
if (
|
|
231
|
+
if (schema.default !== undefined) {
|
|
218
232
|
resolvedSchema = resolvedSchema.default(schema.default)
|
|
219
233
|
}
|
|
220
234
|
|
|
@@ -319,7 +333,7 @@ class SchemaResolver {
|
|
|
319
333
|
|
|
320
334
|
Hoek.assert(joischema, 'Could not resolve type: ' + schema.type + '.');
|
|
321
335
|
|
|
322
|
-
return joischema
|
|
336
|
+
return joischema;
|
|
323
337
|
}
|
|
324
338
|
|
|
325
339
|
if (Array.isArray(schema.type)) {
|
|
@@ -359,7 +373,7 @@ class SchemaResolver {
|
|
|
359
373
|
example = schema.enum[0];
|
|
360
374
|
}
|
|
361
375
|
if (example) {
|
|
362
|
-
joischema = joischema.example(example);
|
|
376
|
+
joischema = joischema.type === 'array' && !Array.isArray(example) ? joischema.example([example]) : joischema.example(example);
|
|
363
377
|
}
|
|
364
378
|
|
|
365
379
|
return joischema;
|
|
@@ -426,7 +440,7 @@ class SchemaResolver {
|
|
|
426
440
|
|
|
427
441
|
if (isObject(schema.additionalProperties)) {
|
|
428
442
|
joischema = joischema.pattern(/^/, this.resolve(schema.additionalProperties, baseUri));
|
|
429
|
-
} else {
|
|
443
|
+
} else if (typeof this.joiOptions.allowUnknown === 'undefined') {
|
|
430
444
|
joischema = joischema.unknown(schema.additionalProperties !== false);
|
|
431
445
|
}
|
|
432
446
|
|