joi-to-json 2.6.1 → 3.0.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 +3 -0
- package/README.md +4 -0
- package/index.js +2 -2
- package/lib/convertors/v12.js +258 -247
- package/lib/parsers/json-draft-04.js +25 -21
- package/lib/parsers/json.js +87 -1
- package/lib/parsers/open-api.js +83 -83
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
|
@@ -249,6 +249,10 @@ For generating JSON Schema in a browser you should use below import syntax for `
|
|
|
249
249
|
import Joi from 'joi/lib/index';
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
+
## Special Joi Operator Support
|
|
253
|
+
|
|
254
|
+
* [Logical Relation Operator](./docs/logical_rel_support.md)
|
|
255
|
+
|
|
252
256
|
## Test
|
|
253
257
|
|
|
254
258
|
>npm run test
|
package/index.js
CHANGED
|
@@ -22,7 +22,7 @@ const parsers = {
|
|
|
22
22
|
'open-api': JoiOpenApiSchemaParser
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function parse(joiObj, type = 'json', definitions = {}) {
|
|
25
|
+
function parse(joiObj, type = 'json', definitions = {}, parserOptions = {}) {
|
|
26
26
|
if (typeof joiObj.describe !== 'function') {
|
|
27
27
|
throw new Error('Not an joi object.')
|
|
28
28
|
}
|
|
@@ -59,7 +59,7 @@ function parse(joiObj, type = 'json', definitions = {}) {
|
|
|
59
59
|
throw new Error(`No parser is registered for ${type}`)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
return new parser().parse(joiBaseSpec, definitions)
|
|
62
|
+
return new parser(parserOptions).parse(joiBaseSpec, definitions)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
module.exports = parse
|
package/lib/convertors/v12.js
CHANGED
|
@@ -1,247 +1,258 @@
|
|
|
1
|
-
const _ = require('lodash')
|
|
2
|
-
|
|
3
|
-
class JoiSpecConvertor {
|
|
4
|
-
constructor() {}
|
|
5
|
-
|
|
6
|
-
static getVersion(joiObj) {
|
|
7
|
-
return joiObj._currentJoi.version
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
static getSupportVersion() {
|
|
11
|
-
return '12'
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
_convertObject(joiObj) {
|
|
15
|
-
if (joiObj.children) {
|
|
16
|
-
joiObj.keys = joiObj.children
|
|
17
|
-
delete joiObj.children
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
_.each(joiObj.keys, (value, _key) => {
|
|
21
|
-
this.toBaseSpec(value)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
_.each(joiObj.patterns, (pattern, _idx) => {
|
|
25
|
-
this.toBaseSpec(pattern.rule)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (joiObj.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
joiObj.whens
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
rule.name = '
|
|
119
|
-
rule.args
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
rule.args =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
rule.args = {
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
break
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
1
|
+
const _ = require('lodash')
|
|
2
|
+
|
|
3
|
+
class JoiSpecConvertor {
|
|
4
|
+
constructor() {}
|
|
5
|
+
|
|
6
|
+
static getVersion(joiObj) {
|
|
7
|
+
return joiObj._currentJoi.version
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static getSupportVersion() {
|
|
11
|
+
return '12'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_convertObject(joiObj) {
|
|
15
|
+
if (joiObj.children) {
|
|
16
|
+
joiObj.keys = joiObj.children
|
|
17
|
+
delete joiObj.children
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_.each(joiObj.keys, (value, _key) => {
|
|
21
|
+
this.toBaseSpec(value)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
_.each(joiObj.patterns, (pattern, _idx) => {
|
|
25
|
+
this.toBaseSpec(pattern.rule)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
this._convertDependencies(joiObj)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_convertDependencies(joiObj) {
|
|
32
|
+
_.each(joiObj.dependencies, (dependency) => {
|
|
33
|
+
if (dependency.type) {
|
|
34
|
+
dependency.rel = dependency.type
|
|
35
|
+
delete dependency.type
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_convertDate(joiObj) {
|
|
41
|
+
if (joiObj.flags.timestamp) {
|
|
42
|
+
joiObj.flags.format = joiObj.flags.timestamp
|
|
43
|
+
delete joiObj.flags.timestamp
|
|
44
|
+
delete joiObj.flags.multiplier
|
|
45
|
+
} else if (joiObj.flags.format = {}) {
|
|
46
|
+
joiObj.flags.format = 'iso'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_convertAlternatives(joiObj) {
|
|
51
|
+
let isWhenCase = false
|
|
52
|
+
if (joiObj.alternatives.length === 1) {
|
|
53
|
+
const condition = joiObj.alternatives[0]
|
|
54
|
+
isWhenCase = !!condition.then || !!condition.otherwise
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isWhenCase) {
|
|
58
|
+
// when case
|
|
59
|
+
joiObj.type = 'any'
|
|
60
|
+
joiObj.whens = joiObj.alternatives
|
|
61
|
+
|
|
62
|
+
if (joiObj.flags.presence === 'ignore') {
|
|
63
|
+
// FIXME: Not sure when this flag is set.
|
|
64
|
+
delete joiObj.flags.presence
|
|
65
|
+
}
|
|
66
|
+
if (joiObj.whens.peek) {
|
|
67
|
+
joiObj.whens.is = joiObj.whens.peek
|
|
68
|
+
delete joiObj.whens.peek
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
joiObj.whens[0] = _.mapValues(joiObj.whens[0], (value, condition) => {
|
|
72
|
+
switch (condition) {
|
|
73
|
+
case 'is':
|
|
74
|
+
this.toBaseSpec(value)
|
|
75
|
+
value.type = 'any'
|
|
76
|
+
// FIXME: Not sure when this is set.
|
|
77
|
+
value.allow.splice(0, 0, { 'override': true })
|
|
78
|
+
break
|
|
79
|
+
case 'otherwise':
|
|
80
|
+
this.toBaseSpec(value)
|
|
81
|
+
break
|
|
82
|
+
case 'then':
|
|
83
|
+
this.toBaseSpec(value)
|
|
84
|
+
break
|
|
85
|
+
case 'ref':
|
|
86
|
+
if (value.indexOf('ref:') === 0) {
|
|
87
|
+
value = {
|
|
88
|
+
path: value.replace('ref:', '').split('.')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
return value
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
delete joiObj.alternatives
|
|
97
|
+
delete joiObj.base
|
|
98
|
+
} else {
|
|
99
|
+
joiObj.matches = _.map(joiObj.alternatives, (alternative) => {
|
|
100
|
+
return {
|
|
101
|
+
schema: this.toBaseSpec(alternative)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
delete joiObj.alternatives
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_convertString(joiObj) {
|
|
109
|
+
delete joiObj.options
|
|
110
|
+
|
|
111
|
+
_.each(joiObj.rules, (rule, _idx) => {
|
|
112
|
+
const name = rule.name
|
|
113
|
+
if (['min', 'max'].includes(name)) {
|
|
114
|
+
rule.args = {
|
|
115
|
+
limit: rule.arg
|
|
116
|
+
}
|
|
117
|
+
} else if (['lowercase', 'uppercase'].includes(name)) {
|
|
118
|
+
rule.name = 'case'
|
|
119
|
+
rule.args = {
|
|
120
|
+
direction: name.replace('case', '')
|
|
121
|
+
}
|
|
122
|
+
delete joiObj.flags.case
|
|
123
|
+
} else if (rule.arg) {
|
|
124
|
+
rule.args = {
|
|
125
|
+
options: rule.arg
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (name === 'regex') {
|
|
129
|
+
rule.name = 'pattern'
|
|
130
|
+
rule.args.regex = rule.arg.pattern.toString()
|
|
131
|
+
delete rule.args.options.pattern
|
|
132
|
+
}
|
|
133
|
+
delete rule.arg
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
_convertNumber(joiObj) {
|
|
138
|
+
_.each(joiObj.rules, (rule, _idx) => {
|
|
139
|
+
const name = rule.name
|
|
140
|
+
if (['positive', 'negative'].includes(name)) {
|
|
141
|
+
rule.args = {
|
|
142
|
+
sign: name
|
|
143
|
+
}
|
|
144
|
+
rule.name = 'sign'
|
|
145
|
+
}
|
|
146
|
+
if (['greater', 'less', 'min', 'max', 'precision'].includes(name)) {
|
|
147
|
+
rule.args = {
|
|
148
|
+
limit: rule.arg
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (name === 'multiple') {
|
|
152
|
+
rule.args = {
|
|
153
|
+
base: rule.arg
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
delete rule.arg
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
if (joiObj.flags.precision) {
|
|
160
|
+
delete joiObj.flags.precision
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_convertBoolean(joiObj) {
|
|
165
|
+
if (joiObj.flags.insensitive === false) {
|
|
166
|
+
joiObj.flags.sensitive = true
|
|
167
|
+
}
|
|
168
|
+
delete joiObj.flags.insensitive
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
_convertArray(joiObj) {
|
|
172
|
+
if (joiObj.flags.sparse === false) {
|
|
173
|
+
delete joiObj.flags.sparse
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_.each(joiObj.rules, (rule, _idx) => {
|
|
177
|
+
const name = rule.name
|
|
178
|
+
if (['min', 'max', 'length'].includes(name)) {
|
|
179
|
+
rule.args = {
|
|
180
|
+
limit: rule.arg
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
delete rule.arg
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
_.each(joiObj.items, (item, _idx) => {
|
|
187
|
+
this.toBaseSpec(item)
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
_convertExamples(_joiObj) {
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
toBaseSpec(joiObj) {
|
|
195
|
+
joiObj.flags = joiObj.flags || {}
|
|
196
|
+
|
|
197
|
+
if (joiObj.flags.allowOnly) {
|
|
198
|
+
joiObj.flags.only = joiObj.flags.allowOnly
|
|
199
|
+
delete joiObj.flags.allowOnly
|
|
200
|
+
}
|
|
201
|
+
if (joiObj.flags.allowUnknown) {
|
|
202
|
+
joiObj.flags.unknown = joiObj.flags.allowUnknown
|
|
203
|
+
delete joiObj.flags.allowUnknown
|
|
204
|
+
}
|
|
205
|
+
if (joiObj.description) {
|
|
206
|
+
joiObj.flags.description = joiObj.description
|
|
207
|
+
delete joiObj.description
|
|
208
|
+
}
|
|
209
|
+
if (joiObj.options) {
|
|
210
|
+
joiObj.preferences = joiObj.options
|
|
211
|
+
delete joiObj.options
|
|
212
|
+
}
|
|
213
|
+
if (joiObj.valids) {
|
|
214
|
+
joiObj.allow = joiObj.valids
|
|
215
|
+
delete joiObj.valids
|
|
216
|
+
}
|
|
217
|
+
if (joiObj.meta) {
|
|
218
|
+
joiObj.metas = joiObj.meta
|
|
219
|
+
delete joiObj.meta
|
|
220
|
+
}
|
|
221
|
+
delete joiObj.invalids
|
|
222
|
+
|
|
223
|
+
this._convertExamples(joiObj)
|
|
224
|
+
|
|
225
|
+
switch (joiObj.type) {
|
|
226
|
+
case 'object':
|
|
227
|
+
this._convertObject(joiObj)
|
|
228
|
+
break
|
|
229
|
+
case 'date':
|
|
230
|
+
this._convertDate(joiObj)
|
|
231
|
+
break
|
|
232
|
+
case 'alternatives':
|
|
233
|
+
this._convertAlternatives(joiObj)
|
|
234
|
+
break
|
|
235
|
+
case 'string':
|
|
236
|
+
this._convertString(joiObj)
|
|
237
|
+
break
|
|
238
|
+
case 'number':
|
|
239
|
+
this._convertNumber(joiObj)
|
|
240
|
+
break
|
|
241
|
+
case 'boolean':
|
|
242
|
+
this._convertBoolean(joiObj)
|
|
243
|
+
break
|
|
244
|
+
case 'array':
|
|
245
|
+
this._convertArray(joiObj)
|
|
246
|
+
break
|
|
247
|
+
default:
|
|
248
|
+
break
|
|
249
|
+
}
|
|
250
|
+
if (_.isEmpty(joiObj.flags)) {
|
|
251
|
+
delete joiObj.flags
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return joiObj
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
module.exports = JoiSpecConvertor
|
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
const _ = require('lodash')
|
|
2
|
-
const JoiJsonSchemaParser = require('./json')
|
|
3
|
-
|
|
4
|
-
class JoiJsonDraftSchemaParser extends JoiJsonSchemaParser {
|
|
5
|
-
|
|
6
|
-
super.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
const _ = require('lodash')
|
|
2
|
+
const JoiJsonSchemaParser = require('./json')
|
|
3
|
+
|
|
4
|
+
class JoiJsonDraftSchemaParser extends JoiJsonSchemaParser {
|
|
5
|
+
constructor(opts = {}) {
|
|
6
|
+
super(_.merge({ logicalOpParser: { xor: null } }, opts))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
_setNumberFieldProperties(fieldSchema, fieldDefn) {
|
|
10
|
+
super._setNumberFieldProperties(fieldSchema, fieldDefn)
|
|
11
|
+
|
|
12
|
+
if (typeof fieldSchema.minimum !== 'undefined' && fieldSchema.minimum === fieldSchema.exclusiveMinimum) {
|
|
13
|
+
fieldSchema.exclusiveMinimum = true
|
|
14
|
+
}
|
|
15
|
+
if (typeof fieldSchema.maximum !== 'undefined' && fieldSchema.maximum === fieldSchema.exclusiveMaximum) {
|
|
16
|
+
fieldSchema.exclusiveMaximum = true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_getLocalSchemaBasePath() {
|
|
21
|
+
return '#/definitions'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = JoiJsonDraftSchemaParser
|
package/lib/parsers/json.js
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
/* eslint no-use-before-define: 'off' */
|
|
2
2
|
const _ = require('lodash')
|
|
3
|
+
const combinations = require('combinations')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default Joi logical operator (or/and/nand/xor/oxor) parsers
|
|
7
|
+
*/
|
|
8
|
+
const LOGICAL_OP_PARSER = {
|
|
9
|
+
or: function (schema, dependency) {
|
|
10
|
+
schema.anyOf = _.map(dependency.peers, (peer) => {
|
|
11
|
+
return { required: [peer] }
|
|
12
|
+
})
|
|
13
|
+
},
|
|
14
|
+
and: function (schema, dependency) {
|
|
15
|
+
schema.oneOf = [{ required: dependency.peers }]
|
|
16
|
+
schema.oneOf.push({
|
|
17
|
+
allOf: _.map(dependency.peers, (peer) => {
|
|
18
|
+
return { not: { required: [peer] } }
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
},
|
|
22
|
+
nand: function (schema, dependency) {
|
|
23
|
+
schema.not = { required: dependency.peers }
|
|
24
|
+
},
|
|
25
|
+
xor: function (schema, dependency) {
|
|
26
|
+
schema.if = {
|
|
27
|
+
propertyNames: { enum: dependency.peers },
|
|
28
|
+
minProperties: 2
|
|
29
|
+
}
|
|
30
|
+
schema.then = false
|
|
31
|
+
schema.else = {
|
|
32
|
+
oneOf: _.map(dependency.peers, (peer) => {
|
|
33
|
+
return { required: [peer] }
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
oxor: function (schema, dependency) {
|
|
38
|
+
schema.oneOf = _.map(dependency.peers, (peer) => {
|
|
39
|
+
return { required: [peer] }
|
|
40
|
+
})
|
|
41
|
+
schema.oneOf.push({
|
|
42
|
+
not: {
|
|
43
|
+
oneOf: _.map(combinations(dependency.peers, 1, 2), (combination) => {
|
|
44
|
+
return { required: combination }
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
3
50
|
|
|
4
51
|
/**
|
|
5
52
|
* Recognize the `joi.override` representation in `describe()` output.
|
|
@@ -20,12 +67,18 @@ function isJoiOverride(e) {
|
|
|
20
67
|
}
|
|
21
68
|
|
|
22
69
|
class JoiJsonSchemaParser {
|
|
23
|
-
constructor() {
|
|
70
|
+
constructor(opts = {}) {
|
|
24
71
|
this.childrenFieldName = this._getChildrenFieldName()
|
|
25
72
|
this.optionsFieldName = this._getOptionsFieldName()
|
|
26
73
|
this.ruleArgFieldName = this._getRuleArgFieldName()
|
|
27
74
|
this.enumFieldName = this._getEnumFieldName()
|
|
28
75
|
this.allowUnknownFlagName = this._getAllowUnknownFlagName()
|
|
76
|
+
|
|
77
|
+
if (opts.logicalOpParser === false) {
|
|
78
|
+
this.logicalOpParser = {}
|
|
79
|
+
} else {
|
|
80
|
+
this.logicalOpParser = _.merge({}, LOGICAL_OP_PARSER, opts.logicalOpParser)
|
|
81
|
+
}
|
|
29
82
|
}
|
|
30
83
|
|
|
31
84
|
parse(joiSpec, definitions = {}, level = 0) {
|
|
@@ -241,6 +294,39 @@ class JoiJsonSchemaParser {
|
|
|
241
294
|
if (_.isEmpty(schema.required)) {
|
|
242
295
|
delete schema.required
|
|
243
296
|
}
|
|
297
|
+
|
|
298
|
+
this._setObjectDependencies(schema, joiSpec)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
_setObjectDependencies(schema, joiSpec) {
|
|
302
|
+
if (!_.isArray(joiSpec.dependencies) || joiSpec.dependencies.length === 0) {
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (joiSpec.dependencies.length === 1) {
|
|
307
|
+
this._setDependencySubSchema(schema, joiSpec.dependencies[0])
|
|
308
|
+
} else {
|
|
309
|
+
schema.allOf = _.compact(_.map(joiSpec.dependencies, (dependency) => {
|
|
310
|
+
const subSchema = this._setDependencySubSchema({}, dependency)
|
|
311
|
+
if (_.isEmpty(subSchema)) {
|
|
312
|
+
return null
|
|
313
|
+
}
|
|
314
|
+
return subSchema
|
|
315
|
+
}))
|
|
316
|
+
if (schema.allOf.length === 0) {
|
|
317
|
+
// When the logicalOpParser is set to false
|
|
318
|
+
delete schema.allOf
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_setDependencySubSchema(schema, dependency) {
|
|
324
|
+
const opParser = this.logicalOpParser[dependency.rel]
|
|
325
|
+
if (typeof opParser !== 'function') {
|
|
326
|
+
return schema
|
|
327
|
+
}
|
|
328
|
+
opParser(schema, dependency)
|
|
329
|
+
return schema
|
|
244
330
|
}
|
|
245
331
|
|
|
246
332
|
_setNumberFieldProperties(fieldSchema, fieldDefn) {
|
package/lib/parsers/open-api.js
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
const _ = require('lodash')
|
|
2
|
-
const JoiJsonSchemaParser = require('./json-draft-04')
|
|
3
|
-
|
|
4
|
-
class JoiOpenApiSchemaParser extends JoiJsonSchemaParser {
|
|
5
|
-
parse(joiSpec, definitions = {}, level = 0) {
|
|
6
|
-
const fullSchema = super.parse(joiSpec, definitions, level)
|
|
7
|
-
const schema = _.pick(fullSchema, [
|
|
8
|
-
'$ref',
|
|
9
|
-
'title',
|
|
10
|
-
'multipleOf',
|
|
11
|
-
'maximum',
|
|
12
|
-
'exclusiveMaximum',
|
|
13
|
-
'minimum',
|
|
14
|
-
'exclusiveMinimum',
|
|
15
|
-
'maxLength',
|
|
16
|
-
'minLength',
|
|
17
|
-
'pattern',
|
|
18
|
-
'maxItems',
|
|
19
|
-
'minItems',
|
|
20
|
-
'uniqueItems',
|
|
21
|
-
'maxProperties',
|
|
22
|
-
'minProperties',
|
|
23
|
-
'required',
|
|
24
|
-
'enum',
|
|
25
|
-
'description',
|
|
26
|
-
'format',
|
|
27
|
-
'default',
|
|
28
|
-
'type',
|
|
29
|
-
|
|
30
|
-
'allOf',
|
|
31
|
-
'oneOf',
|
|
32
|
-
'anyOf',
|
|
33
|
-
'not',
|
|
34
|
-
'items',
|
|
35
|
-
'properties',
|
|
36
|
-
'additionalProperties',
|
|
37
|
-
|
|
38
|
-
'example',
|
|
39
|
-
'nullable'
|
|
40
|
-
])
|
|
41
|
-
|
|
42
|
-
if (!_.isEmpty(joiSpec.metas)) {
|
|
43
|
-
_.each(joiSpec.metas, meta => {
|
|
44
|
-
_.each(meta, (value, key) => {
|
|
45
|
-
if (key.startsWith('x-') || key === 'deprecated' || key === 'readOnly' || key === 'writeOnly') {
|
|
46
|
-
schema[key] = value
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (level === 0 && !_.isEmpty(definitions)) {
|
|
53
|
-
schema.schemas = definitions
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (fullSchema.const) {
|
|
57
|
-
schema.enum = [fullSchema.const]
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return schema
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
_getLocalSchemaBasePath() {
|
|
64
|
-
return '#/components/schemas'
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
_setBasicProperties(fieldSchema, fieldDefn) {
|
|
68
|
-
super._setBasicProperties(fieldSchema, fieldDefn)
|
|
69
|
-
|
|
70
|
-
if (!_.isEmpty(fieldSchema.examples)) {
|
|
71
|
-
fieldSchema.example = fieldSchema.examples[0]
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
_addNullTypeIfNullable(fieldSchema, fieldDefn) {
|
|
76
|
-
const enums = _.get(fieldDefn, this.enumFieldName)
|
|
77
|
-
if (Array.isArray(enums) && enums.includes(null)) {
|
|
78
|
-
fieldSchema.nullable = true
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
module.exports = JoiOpenApiSchemaParser
|
|
1
|
+
const _ = require('lodash')
|
|
2
|
+
const JoiJsonSchemaParser = require('./json-draft-04')
|
|
3
|
+
|
|
4
|
+
class JoiOpenApiSchemaParser extends JoiJsonSchemaParser {
|
|
5
|
+
parse(joiSpec, definitions = {}, level = 0) {
|
|
6
|
+
const fullSchema = super.parse(joiSpec, definitions, level)
|
|
7
|
+
const schema = _.pick(fullSchema, [
|
|
8
|
+
'$ref',
|
|
9
|
+
'title',
|
|
10
|
+
'multipleOf',
|
|
11
|
+
'maximum',
|
|
12
|
+
'exclusiveMaximum',
|
|
13
|
+
'minimum',
|
|
14
|
+
'exclusiveMinimum',
|
|
15
|
+
'maxLength',
|
|
16
|
+
'minLength',
|
|
17
|
+
'pattern',
|
|
18
|
+
'maxItems',
|
|
19
|
+
'minItems',
|
|
20
|
+
'uniqueItems',
|
|
21
|
+
'maxProperties',
|
|
22
|
+
'minProperties',
|
|
23
|
+
'required',
|
|
24
|
+
'enum',
|
|
25
|
+
'description',
|
|
26
|
+
'format',
|
|
27
|
+
'default',
|
|
28
|
+
'type',
|
|
29
|
+
|
|
30
|
+
'allOf',
|
|
31
|
+
'oneOf',
|
|
32
|
+
'anyOf',
|
|
33
|
+
'not',
|
|
34
|
+
'items',
|
|
35
|
+
'properties',
|
|
36
|
+
'additionalProperties',
|
|
37
|
+
|
|
38
|
+
'example',
|
|
39
|
+
'nullable'
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
if (!_.isEmpty(joiSpec.metas)) {
|
|
43
|
+
_.each(joiSpec.metas, meta => {
|
|
44
|
+
_.each(meta, (value, key) => {
|
|
45
|
+
if (key.startsWith('x-') || key === 'deprecated' || key === 'readOnly' || key === 'writeOnly') {
|
|
46
|
+
schema[key] = value
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (level === 0 && !_.isEmpty(definitions)) {
|
|
53
|
+
schema.schemas = definitions
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (fullSchema.const) {
|
|
57
|
+
schema.enum = [fullSchema.const]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return schema
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_getLocalSchemaBasePath() {
|
|
64
|
+
return '#/components/schemas'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_setBasicProperties(fieldSchema, fieldDefn) {
|
|
68
|
+
super._setBasicProperties(fieldSchema, fieldDefn)
|
|
69
|
+
|
|
70
|
+
if (!_.isEmpty(fieldSchema.examples)) {
|
|
71
|
+
fieldSchema.example = fieldSchema.examples[0]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_addNullTypeIfNullable(fieldSchema, fieldDefn) {
|
|
76
|
+
const enums = _.get(fieldDefn, this.enumFieldName)
|
|
77
|
+
if (Array.isArray(enums) && enums.includes(null)) {
|
|
78
|
+
fieldSchema.nullable = true
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = JoiOpenApiSchemaParser
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "joi-to-json",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "joi to JSON / OpenAPI Schema Converter",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"oas"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"combinations": "^1.0.0",
|
|
26
27
|
"lodash": "^4.17.21",
|
|
27
28
|
"semver-compare": "^1.0.0"
|
|
28
29
|
},
|