joi-to-json 2.3.6 → 2.6.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/README.md +125 -118
- package/index.d.ts +18 -0
- package/index.js +65 -64
- package/lib/parsers/json.js +484 -484
- package/lib/parsers/open-api.js +83 -83
- package/package.json +47 -45
package/README.md
CHANGED
|
@@ -1,118 +1,125 @@
|
|
|
1
|
-
# joi-to-json
|
|
2
|
-
|
|
3
|
-
## Objective
|
|
4
|
-
|
|
5
|
-
I have been using [joi](https://joi.dev/) a lot in different Node.js projects to guard the API.
|
|
6
|
-
It's **The most powerful schema description language and data validator for JavaScript.** as it said.
|
|
7
|
-
|
|
8
|
-
Many times, we need to utilize this schema description to produce other output, such as Swagger OpenAPI doc.
|
|
9
|
-
That is why I build [joi-route-to-swagger](https://github.com/kenspirit/joi-route-to-swagger) in the first place.
|
|
10
|
-
|
|
11
|
-
At the beginning, `joi-route-to-swagger` relies on [joi-to-json-schema](https://github.com/lightsofapollo/joi-to-json-schema/) which utilizes many joi internal api or properties. I believed there was reason. Maybe joi did not provide the `describe` api way before. But I always feel uncomfortable and think it's time to move on.
|
|
12
|
-
|
|
13
|
-
The intention of `joi-to-json` is to support converting different version's joi schema to [JSON Schema](https://json-schema.org) using `describe` api.
|
|
14
|
-
|
|
15
|
-
## 2.0.0 is out
|
|
16
|
-
|
|
17
|
-
It's a breaking change.
|
|
18
|
-
|
|
19
|
-
* Functionally, output format supports OpenAPI Schema other than purely JSON Schema.
|
|
20
|
-
|
|
21
|
-
* Technically, implementation theory has a big change:
|
|
22
|
-
- In v1.0.0, it directly converts `joi.describe()` to JSON schema using different parser implementations.
|
|
23
|
-
- In v2.0.0, `joi.describe()` of different versions are first converted to one base format, the latest version of `joi.describe()` output. Then different parsers (JSON, OpenAPI) all refer to this base format.
|
|
24
|
-
|
|
25
|
-
* The benefits of the change are:
|
|
26
|
-
- Easier to retire old version of joi.
|
|
27
|
-
- Easier to support more output formats.
|
|
28
|
-
|
|
29
|
-
## Installation
|
|
30
|
-
|
|
31
|
-
>npm install joi-to-json
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
## Joi Version Support
|
|
35
|
-
|
|
36
|
-
* @commercial/joi
|
|
37
|
-
* v12.1.0
|
|
38
|
-
* joi
|
|
39
|
-
* 13.7.0
|
|
40
|
-
* 14.3.1
|
|
41
|
-
* @hapi/joi
|
|
42
|
-
* 15.1.1
|
|
43
|
-
* 16.1.8
|
|
44
|
-
* joi
|
|
45
|
-
* 17.4.2
|
|
46
|
-
|
|
47
|
-
For all above versions, I have tested one complex joi object [fixtures](./fixtures) which covers most of the JSON schema attributes that can be described in joi schema.
|
|
48
|
-
|
|
49
|
-
Although the versions chosen are the latest one for each major version, I believe it should be supporting other minor version as well.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
## Usage
|
|
53
|
-
|
|
54
|
-
Only one API `parse` is available. It's signature is `parse(joiObj, type = 'json')`
|
|
55
|
-
|
|
56
|
-
Currently supported output types:
|
|
57
|
-
* `json` - Default. Stands for JSON Schema Draft 07
|
|
58
|
-
* `open-api` - Stands for OpenAPI Schema
|
|
59
|
-
* `json-draft-04` - Stands for JSON Schema Draft 04
|
|
60
|
-
* `json-draft-2019-09` - Stands for JSON Schema Draft 2019-09
|
|
61
|
-
|
|
62
|
-
The output schema format are in [outputs](./outputs) under specific folders for different types.
|
|
63
|
-
|
|
64
|
-
Sample code is as below:
|
|
65
|
-
|
|
66
|
-
```javascript
|
|
67
|
-
const parse = require('joi-to-json')
|
|
68
|
-
|
|
69
|
-
const joiSchema = joi.object().keys({
|
|
70
|
-
nickName: joi.string().required().min(3).max(20).example('鹄思乱想').description('Hero Nickname')
|
|
71
|
-
.regex(/^[a-z]+$/, { name: 'alpha', invert: true }),
|
|
72
|
-
avatar: joi.string().required().uri(),
|
|
73
|
-
email: joi.string().email(),
|
|
74
|
-
ip: joi.string().ip({ version: ['ipv4', 'ipv6'] }),
|
|
75
|
-
hostname: joi.string().hostname().insensitive(),
|
|
76
|
-
gender: joi.string().valid('Male', 'Female', '').default('Male'),
|
|
77
|
-
height: joi.number().precision(2).positive().greater(0).less(200),
|
|
78
|
-
birthday: joi.date().iso(),
|
|
79
|
-
birthTime: joi.date().timestamp('unix'),
|
|
80
|
-
skills: joi.array().items(joi.alternatives().try(
|
|
81
|
-
joi.string(),
|
|
82
|
-
joi.object().keys({
|
|
83
|
-
name: joi.string().example('teleport').alphanum().lowercase().required().description('Skill Name'),
|
|
84
|
-
level: joi.number().integer().min(10).max(100).default(50).multiple(10).example(10).description('Skill Level')
|
|
85
|
-
})
|
|
86
|
-
).required()).min(1).max(3).unique().description('Skills'),
|
|
87
|
-
tags: joi.array().items(joi.string().required()).length(2),
|
|
88
|
-
retired: joi.boolean().truthy('yes').falsy('no').insensitive(false),
|
|
89
|
-
certificate: joi.binary().encoding('base64'),
|
|
90
|
-
notes: joi.any().meta({ 'x-supported-lang': ['zh-CN', 'en-US'], deprecated: true })
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
const jsonSchema = parse(joiSchema)
|
|
94
|
-
// Or parsing to OpenAPI schema through:
|
|
95
|
-
// const openApiSchema = parse(joiSchema, 'open-api')
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
##
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
1
|
+
# joi-to-json
|
|
2
|
+
|
|
3
|
+
## Objective
|
|
4
|
+
|
|
5
|
+
I have been using [joi](https://joi.dev/) a lot in different Node.js projects to guard the API.
|
|
6
|
+
It's **The most powerful schema description language and data validator for JavaScript.** as it said.
|
|
7
|
+
|
|
8
|
+
Many times, we need to utilize this schema description to produce other output, such as Swagger OpenAPI doc.
|
|
9
|
+
That is why I build [joi-route-to-swagger](https://github.com/kenspirit/joi-route-to-swagger) in the first place.
|
|
10
|
+
|
|
11
|
+
At the beginning, `joi-route-to-swagger` relies on [joi-to-json-schema](https://github.com/lightsofapollo/joi-to-json-schema/) which utilizes many joi internal api or properties. I believed there was reason. Maybe joi did not provide the `describe` api way before. But I always feel uncomfortable and think it's time to move on.
|
|
12
|
+
|
|
13
|
+
The intention of `joi-to-json` is to support converting different version's joi schema to [JSON Schema](https://json-schema.org) using `describe` api.
|
|
14
|
+
|
|
15
|
+
## 2.0.0 is out
|
|
16
|
+
|
|
17
|
+
It's a breaking change.
|
|
18
|
+
|
|
19
|
+
* Functionally, output format supports OpenAPI Schema other than purely JSON Schema.
|
|
20
|
+
|
|
21
|
+
* Technically, implementation theory has a big change:
|
|
22
|
+
- In v1.0.0, it directly converts `joi.describe()` to JSON schema using different parser implementations.
|
|
23
|
+
- In v2.0.0, `joi.describe()` of different versions are first converted to one base format, the latest version of `joi.describe()` output. Then different parsers (JSON, OpenAPI) all refer to this base format.
|
|
24
|
+
|
|
25
|
+
* The benefits of the change are:
|
|
26
|
+
- Easier to retire old version of joi.
|
|
27
|
+
- Easier to support more output formats.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
>npm install joi-to-json
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Joi Version Support
|
|
35
|
+
|
|
36
|
+
* @commercial/joi
|
|
37
|
+
* v12.1.0
|
|
38
|
+
* joi
|
|
39
|
+
* 13.7.0
|
|
40
|
+
* 14.3.1
|
|
41
|
+
* @hapi/joi
|
|
42
|
+
* 15.1.1
|
|
43
|
+
* 16.1.8
|
|
44
|
+
* joi
|
|
45
|
+
* 17.4.2
|
|
46
|
+
|
|
47
|
+
For all above versions, I have tested one complex joi object [fixtures](./fixtures) which covers most of the JSON schema attributes that can be described in joi schema.
|
|
48
|
+
|
|
49
|
+
Although the versions chosen are the latest one for each major version, I believe it should be supporting other minor version as well.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
Only one API `parse` is available. It's signature is `parse(joiObj, type = 'json')`
|
|
55
|
+
|
|
56
|
+
Currently supported output types:
|
|
57
|
+
* `json` - Default. Stands for JSON Schema Draft 07
|
|
58
|
+
* `open-api` - Stands for OpenAPI Schema
|
|
59
|
+
* `json-draft-04` - Stands for JSON Schema Draft 04
|
|
60
|
+
* `json-draft-2019-09` - Stands for JSON Schema Draft 2019-09
|
|
61
|
+
|
|
62
|
+
The output schema format are in [outputs](./outputs) under specific folders for different types.
|
|
63
|
+
|
|
64
|
+
Sample code is as below:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const parse = require('joi-to-json')
|
|
68
|
+
|
|
69
|
+
const joiSchema = joi.object().keys({
|
|
70
|
+
nickName: joi.string().required().min(3).max(20).example('鹄思乱想').description('Hero Nickname')
|
|
71
|
+
.regex(/^[a-z]+$/, { name: 'alpha', invert: true }),
|
|
72
|
+
avatar: joi.string().required().uri(),
|
|
73
|
+
email: joi.string().email(),
|
|
74
|
+
ip: joi.string().ip({ version: ['ipv4', 'ipv6'] }),
|
|
75
|
+
hostname: joi.string().hostname().insensitive(),
|
|
76
|
+
gender: joi.string().valid('Male', 'Female', '').default('Male'),
|
|
77
|
+
height: joi.number().precision(2).positive().greater(0).less(200),
|
|
78
|
+
birthday: joi.date().iso(),
|
|
79
|
+
birthTime: joi.date().timestamp('unix'),
|
|
80
|
+
skills: joi.array().items(joi.alternatives().try(
|
|
81
|
+
joi.string(),
|
|
82
|
+
joi.object().keys({
|
|
83
|
+
name: joi.string().example('teleport').alphanum().lowercase().required().description('Skill Name'),
|
|
84
|
+
level: joi.number().integer().min(10).max(100).default(50).multiple(10).example(10).description('Skill Level')
|
|
85
|
+
})
|
|
86
|
+
).required()).min(1).max(3).unique().description('Skills'),
|
|
87
|
+
tags: joi.array().items(joi.string().required()).length(2),
|
|
88
|
+
retired: joi.boolean().truthy('yes').falsy('no').insensitive(false),
|
|
89
|
+
certificate: joi.binary().encoding('base64'),
|
|
90
|
+
notes: joi.any().meta({ 'x-supported-lang': ['zh-CN', 'en-US'], deprecated: true })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const jsonSchema = parse(joiSchema)
|
|
94
|
+
// Or parsing to OpenAPI schema through:
|
|
95
|
+
// const openApiSchema = parse(joiSchema, 'open-api')
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Browser support
|
|
99
|
+
For generating JSON Schema in a browser you should use below import syntax for `joi` library in order to work because the `joi` browser minimized build does not have `describe` api which the `joi-to-json` relies on.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import Joi from 'joi/lib/index';
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Test
|
|
106
|
+
|
|
107
|
+
>npm run test
|
|
108
|
+
|
|
109
|
+
You can optionally set below environment variables:
|
|
110
|
+
|
|
111
|
+
* `CASE_PATTERN=joi-obj-17` to control which version of joi obj to test
|
|
112
|
+
|
|
113
|
+
## Known Limitation
|
|
114
|
+
|
|
115
|
+
* For `object.pattern` usage in Joi, `pattern` parameter can only be a regular expression now as I cannot convert Joi object to regex yet.
|
|
116
|
+
|
|
117
|
+
## Updates
|
|
118
|
+
|
|
119
|
+
**Version 2.3.0**
|
|
120
|
+
|
|
121
|
+
* Supports named link for schema resuse, such as `.link('#person')`. **For `open-api` conversion**, as the shared schemas are located in `#/components/schemas` which is not self-contained, the conversion result contains an **extra `schemas`** field so that you can extract it when required.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Joi from 'joi-17';
|
|
2
|
+
|
|
3
|
+
declare module Joi2Json {
|
|
4
|
+
/**
|
|
5
|
+
* @type {string}
|
|
6
|
+
*/
|
|
7
|
+
export type Mode = 'json' | 'open-api' | 'json-draft-2019-09' | 'json-draft-04';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} joi - A Joi schema.
|
|
11
|
+
* @param {string} [mode='json'] - json / open-api / json-draft-2019-09 / json-draft-04
|
|
12
|
+
* @param {Record} [sharedSchema={}] - Passed-in object storing shared schemas
|
|
13
|
+
* @returns {any} Converted JSON schema object.
|
|
14
|
+
*/
|
|
15
|
+
export function parse(joi: Joi.Schema, mode?: Mode, sharedSchema?: Record<string, any>): any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default Joi2Json.parse;
|
package/index.js
CHANGED
|
@@ -1,64 +1,65 @@
|
|
|
1
|
-
const cmp = require('semver-compare')
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
convertor
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
const cmp = require('semver-compare')
|
|
2
|
+
|
|
3
|
+
const c17 = require('./lib/convertors/v17')
|
|
4
|
+
const c16 = require('./lib/convertors/v16')
|
|
5
|
+
const c15 = require('./lib/convertors/v15')
|
|
6
|
+
const c14 = require('./lib/convertors/v14')
|
|
7
|
+
const c13 = require('./lib/convertors/v13')
|
|
8
|
+
const c12 = require('./lib/convertors/v12')
|
|
9
|
+
|
|
10
|
+
const JoiJsonSchemaParser = require('./lib/parsers/json')
|
|
11
|
+
const JoiOpenApiSchemaParser = require('./lib/parsers/open-api')
|
|
12
|
+
const JoiJsonDraftSchemaParser19 = require('./lib/parsers/json-draft-2019-09')
|
|
13
|
+
const JoiJsonDraftSchemaParser = require('./lib/parsers/json-draft-04')
|
|
14
|
+
|
|
15
|
+
const convertors = [
|
|
16
|
+
c17, c16, c15, c14, c13, c12
|
|
17
|
+
]
|
|
18
|
+
const parsers = {
|
|
19
|
+
'json-draft-2019-09': JoiJsonDraftSchemaParser19,
|
|
20
|
+
'json-draft-04': JoiJsonDraftSchemaParser,
|
|
21
|
+
json: JoiJsonSchemaParser,
|
|
22
|
+
'open-api': JoiOpenApiSchemaParser
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parse(joiObj, type = 'json', definitions = {}) {
|
|
26
|
+
if (typeof joiObj.describe !== 'function') {
|
|
27
|
+
throw new Error('Not an joi object.')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let convertor
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < convertors.length; i++) {
|
|
33
|
+
const tmpConvertor = convertors[i]
|
|
34
|
+
try {
|
|
35
|
+
let version = tmpConvertor.getVersion(joiObj)
|
|
36
|
+
let result = cmp(tmpConvertor.getSupportVersion(), version)
|
|
37
|
+
if (result <= 0) {
|
|
38
|
+
// The first parser has smaller or equal version
|
|
39
|
+
convertor = tmpConvertor
|
|
40
|
+
break
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
// Format does not match this parser version.
|
|
44
|
+
// Skip to check the next one
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!convertor) {
|
|
50
|
+
console.warn('No matched joi version convertor found, using the latest version')
|
|
51
|
+
convertor = convertors[0]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// fs.writeFileSync('./joi_spec.json', JSON.stringify(joiObj.describe(), null, 2))
|
|
55
|
+
const joiBaseSpec = new convertor().toBaseSpec(joiObj.describe())
|
|
56
|
+
// fs.writeFileSync(`./internal_${convertor.getSupportVersion()}_${type}.json`, JSON.stringify(joiBaseSpec, null, 2))
|
|
57
|
+
const parser = parsers[type]
|
|
58
|
+
if (!parser) {
|
|
59
|
+
throw new Error(`No parser is registered for ${type}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return new parser().parse(joiBaseSpec, definitions)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = parse
|