joi-to-json 2.2.2 → 2.3.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 +7 -1
- package/index.js +1 -0
- package/lib/parsers/json-draft-04.js +6 -2
- package/lib/parsers/json-draft-2019-09.js +4 -4
- package/lib/parsers/json.js +61 -30
- package/lib/parsers/open-api.js +12 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,12 +101,18 @@ const jsonSchema = parse(joiSchema)
|
|
|
101
101
|
|
|
102
102
|
You can optionally set below environment variables:
|
|
103
103
|
|
|
104
|
-
* `CASE_PATTERN=joi-obj-
|
|
104
|
+
* `CASE_PATTERN=joi-obj-17` to control which version of joi obj to test
|
|
105
105
|
|
|
106
106
|
## Known Limitation
|
|
107
107
|
|
|
108
108
|
* For `object.pattern` usage in Joi, `pattern` parameter can only be a regular expression now as I cannot convert Joi object to regex yet.
|
|
109
109
|
|
|
110
|
+
## Updates
|
|
111
|
+
|
|
112
|
+
**Version 2.3.0**
|
|
113
|
+
|
|
114
|
+
* 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.
|
|
115
|
+
|
|
110
116
|
## License
|
|
111
117
|
|
|
112
118
|
MIT
|
package/index.js
CHANGED
|
@@ -50,6 +50,7 @@ function parse(joiObj, type = 'json') {
|
|
|
50
50
|
convertor = convertors[0]
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// fs.writeFileSync('./joi_spec.json', JSON.stringify(joiObj.describe(), null, 2))
|
|
53
54
|
const joiBaseSpec = new convertor().toBaseSpec(joiObj.describe())
|
|
54
55
|
// fs.writeFileSync(`./internal_${convertor.getSupportVersion()}_${type}.json`, JSON.stringify(joiBaseSpec, null, 2))
|
|
55
56
|
const parser = parsers[type]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const _ = require('lodash')
|
|
2
2
|
const JoiJsonSchemaParser = require('./json')
|
|
3
3
|
|
|
4
|
-
class
|
|
4
|
+
class JoiJsonDraftSchemaParser extends JoiJsonSchemaParser {
|
|
5
5
|
_setNumberFieldProperties(fieldSchema, fieldDefn) {
|
|
6
6
|
super._setNumberFieldProperties(fieldSchema, fieldDefn)
|
|
7
7
|
|
|
@@ -12,6 +12,10 @@ class JoiOpenApiSchemaParser extends JoiJsonSchemaParser {
|
|
|
12
12
|
fieldSchema.exclusiveMaximum = true
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
_getLocalSchemaBasePath() {
|
|
17
|
+
return '#/definitions'
|
|
18
|
+
}
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
module.exports =
|
|
21
|
+
module.exports = JoiJsonDraftSchemaParser
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const _ = require('lodash')
|
|
2
2
|
const JoiJsonSchemaParser = require('./json')
|
|
3
3
|
|
|
4
|
-
class
|
|
5
|
-
parse(joiSpec) {
|
|
6
|
-
const schema = super.parse(joiSpec)
|
|
4
|
+
class JoiJsonDraftSchemaParser extends JoiJsonSchemaParser {
|
|
5
|
+
parse(joiSpec, definitions = {}, level = 0) {
|
|
6
|
+
const schema = super.parse(joiSpec, definitions, level)
|
|
7
7
|
|
|
8
8
|
if (!_.isEmpty(joiSpec.metas)) {
|
|
9
9
|
_.each(joiSpec.metas, meta => {
|
|
@@ -19,4 +19,4 @@ class JoiOpenApiSchemaParser extends JoiJsonSchemaParser {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
module.exports =
|
|
22
|
+
module.exports = JoiJsonDraftSchemaParser
|
package/lib/parsers/json.js
CHANGED
|
@@ -10,8 +10,8 @@ class JoiJsonSchemaParser {
|
|
|
10
10
|
this.allowUnknownFlagName = this._getAllowUnknownFlagName()
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
parse(joiSpec) {
|
|
14
|
-
|
|
13
|
+
parse(joiSpec, definitions = {}, level = 0) {
|
|
14
|
+
let schema = {}
|
|
15
15
|
|
|
16
16
|
if (this._getPresence(joiSpec) === 'forbidden') {
|
|
17
17
|
schema.not = {}
|
|
@@ -23,12 +23,24 @@ class JoiJsonSchemaParser {
|
|
|
23
23
|
this._setBinaryFieldProperties(schema, joiSpec)
|
|
24
24
|
this._setStringFieldProperties(schema, joiSpec)
|
|
25
25
|
this._setDateFieldProperties(schema, joiSpec)
|
|
26
|
-
this._setArrayFieldProperties(schema, joiSpec)
|
|
27
|
-
this._setObjectProperties(schema, joiSpec)
|
|
28
|
-
this._setAlternativesProperties(schema, joiSpec)
|
|
29
|
-
this._setAnyProperties(schema, joiSpec)
|
|
26
|
+
this._setArrayFieldProperties(schema, joiSpec, definitions, level)
|
|
27
|
+
this._setObjectProperties(schema, joiSpec, definitions, level)
|
|
28
|
+
this._setAlternativesProperties(schema, joiSpec, definitions, level)
|
|
29
|
+
this._setAnyProperties(schema, joiSpec, definitions, level)
|
|
30
30
|
this._addNullTypeIfNullable(schema, joiSpec)
|
|
31
31
|
this._setMetaProperties(schema, joiSpec)
|
|
32
|
+
this._setLinkFieldProperties(schema, joiSpec)
|
|
33
|
+
|
|
34
|
+
const schemaId = _.get(joiSpec, 'flags.id')
|
|
35
|
+
if (schemaId) {
|
|
36
|
+
definitions[schemaId] = schema
|
|
37
|
+
schema = {
|
|
38
|
+
$ref: `${this._getLocalSchemaBasePath()}/${schemaId}`
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (level === 0 && !_.isEmpty(definitions)) {
|
|
42
|
+
_.set(schema, `${this._getLocalSchemaBasePath().replace('#/', '').replace(/\//, '.')}`, definitions)
|
|
43
|
+
}
|
|
32
44
|
|
|
33
45
|
return schema
|
|
34
46
|
}
|
|
@@ -53,6 +65,10 @@ class JoiJsonSchemaParser {
|
|
|
53
65
|
return 'unknown'
|
|
54
66
|
}
|
|
55
67
|
|
|
68
|
+
_getLocalSchemaBasePath() {
|
|
69
|
+
return '#/$defs'
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
_getFieldDescription(fieldDefn) {
|
|
57
73
|
return _.get(fieldDefn, 'flags.description')
|
|
58
74
|
}
|
|
@@ -96,14 +112,18 @@ class JoiJsonSchemaParser {
|
|
|
96
112
|
}
|
|
97
113
|
|
|
98
114
|
_getEnum(fieldDefn) {
|
|
99
|
-
|
|
100
|
-
|
|
115
|
+
const enumList = fieldDefn[this.enumFieldName]
|
|
116
|
+
if (fieldDefn.flags && fieldDefn.flags.only && !_.isEmpty(enumList)) {
|
|
117
|
+
return _.uniq(enumList)
|
|
101
118
|
}
|
|
119
|
+
}
|
|
102
120
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
_getUnknown(joiSpec) {
|
|
122
|
+
let allowUnknown = _.get(joiSpec, `${this.optionsFieldName}.allowUnknown`, false)
|
|
123
|
+
if (joiSpec.flags && typeof joiSpec.flags[this.allowUnknownFlagName] !== 'undefined') {
|
|
124
|
+
allowUnknown = joiSpec.flags[this.allowUnknownFlagName]
|
|
125
|
+
}
|
|
126
|
+
return allowUnknown
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
_setIfNotEmpty(schema, field, value) {
|
|
@@ -131,7 +151,7 @@ class JoiJsonSchemaParser {
|
|
|
131
151
|
fieldSchema.format = 'binary'
|
|
132
152
|
}
|
|
133
153
|
|
|
134
|
-
_setObjectProperties(schema, joiSpec) {
|
|
154
|
+
_setObjectProperties(schema, joiSpec, definitions, level) {
|
|
135
155
|
if (schema.type !== 'object') {
|
|
136
156
|
return
|
|
137
157
|
}
|
|
@@ -139,13 +159,10 @@ class JoiJsonSchemaParser {
|
|
|
139
159
|
schema.properties = {}
|
|
140
160
|
schema.required = []
|
|
141
161
|
|
|
142
|
-
schema.additionalProperties =
|
|
143
|
-
if (joiSpec.flags && typeof joiSpec.flags[this.allowUnknownFlagName] !== 'undefined') {
|
|
144
|
-
schema.additionalProperties = joiSpec.flags[this.allowUnknownFlagName]
|
|
145
|
-
}
|
|
162
|
+
schema.additionalProperties = this._getUnknown(joiSpec)
|
|
146
163
|
|
|
147
164
|
_.map(joiSpec[this.childrenFieldName], (fieldDefn, key) => {
|
|
148
|
-
const fieldSchema = this.parse(fieldDefn)
|
|
165
|
+
const fieldSchema = this.parse(fieldDefn, definitions, level + 1)
|
|
149
166
|
if (this._isRequired(fieldDefn)) {
|
|
150
167
|
schema.required.push(key)
|
|
151
168
|
}
|
|
@@ -170,9 +187,10 @@ class JoiJsonSchemaParser {
|
|
|
170
187
|
schema.properties[patternObj.regex].required = []
|
|
171
188
|
|
|
172
189
|
const childKeys = patternObj.rule.keys || patternObj.rule.children
|
|
190
|
+
schema.properties[patternObj.regex].additionalProperties = this._getUnknown(patternObj.rule)
|
|
173
191
|
|
|
174
192
|
_.each(childKeys, (ruleObj, key) => {
|
|
175
|
-
schema.properties[patternObj.regex].properties[key] = this.parse(ruleObj)
|
|
193
|
+
schema.properties[patternObj.regex].properties[key] = this.parse(ruleObj, definitions, level + 1)
|
|
176
194
|
|
|
177
195
|
if (this._isRequired(ruleObj)) {
|
|
178
196
|
schema.properties[patternObj.regex].required.push(key)
|
|
@@ -299,7 +317,7 @@ class JoiJsonSchemaParser {
|
|
|
299
317
|
})
|
|
300
318
|
}
|
|
301
319
|
|
|
302
|
-
_setArrayFieldProperties(fieldSchema, fieldDefn) {
|
|
320
|
+
_setArrayFieldProperties(fieldSchema, fieldDefn, definitions, level) {
|
|
303
321
|
if (fieldSchema.type !== 'array') {
|
|
304
322
|
return
|
|
305
323
|
}
|
|
@@ -333,10 +351,12 @@ class JoiJsonSchemaParser {
|
|
|
333
351
|
}
|
|
334
352
|
|
|
335
353
|
if (fieldDefn.items.length === 1) {
|
|
336
|
-
fieldSchema.items = this.parse(fieldDefn.items[0])
|
|
354
|
+
fieldSchema.items = this.parse(fieldDefn.items[0], definitions, level + 1)
|
|
337
355
|
} else {
|
|
338
356
|
fieldSchema.items = {
|
|
339
|
-
anyOf: _.map(fieldDefn.items,
|
|
357
|
+
anyOf: _.map(fieldDefn.items, (itemSchema) => {
|
|
358
|
+
return this.parse(itemSchema, definitions, level + 1)
|
|
359
|
+
})
|
|
340
360
|
}
|
|
341
361
|
}
|
|
342
362
|
}
|
|
@@ -357,7 +377,7 @@ class JoiJsonSchemaParser {
|
|
|
357
377
|
}
|
|
358
378
|
}
|
|
359
379
|
|
|
360
|
-
_setAlternativesProperties(schema, joiSpec) {
|
|
380
|
+
_setAlternativesProperties(schema, joiSpec, definitions, level) {
|
|
361
381
|
if (schema.type !== 'alternatives') {
|
|
362
382
|
return
|
|
363
383
|
}
|
|
@@ -366,23 +386,23 @@ class JoiJsonSchemaParser {
|
|
|
366
386
|
const match = joiSpec.matches[0]
|
|
367
387
|
if (match.switch) {
|
|
368
388
|
schema.oneOf = _.map(match.switch, (condition) => {
|
|
369
|
-
return this.parse(condition.then || condition.otherwise)
|
|
389
|
+
return this.parse(condition.then || condition.otherwise, definitions, level + 1)
|
|
370
390
|
})
|
|
371
391
|
} else if (match.then || match.otherwise) {
|
|
372
392
|
schema.oneOf = []
|
|
373
|
-
if (match.then) schema.oneOf.push(this.parse(match.then))
|
|
374
|
-
if (match.otherwise) schema.oneOf.push(this.parse(match.otherwise))
|
|
393
|
+
if (match.then) schema.oneOf.push(this.parse(match.then, definitions, level + 1))
|
|
394
|
+
if (match.otherwise) schema.oneOf.push(this.parse(match.otherwise, definitions, level + 1))
|
|
375
395
|
}
|
|
376
396
|
} else {
|
|
377
397
|
schema.oneOf = _.map(joiSpec.matches, (match) => {
|
|
378
|
-
return this.parse(match.schema)
|
|
398
|
+
return this.parse(match.schema, definitions, level + 1)
|
|
379
399
|
})
|
|
380
400
|
}
|
|
381
401
|
|
|
382
402
|
delete schema.type
|
|
383
403
|
}
|
|
384
404
|
|
|
385
|
-
_setAnyProperties(schema, joiSpec) {
|
|
405
|
+
_setAnyProperties(schema, joiSpec, definitions, level) {
|
|
386
406
|
if (schema.type !== 'any') {
|
|
387
407
|
return
|
|
388
408
|
}
|
|
@@ -391,10 +411,10 @@ class JoiJsonSchemaParser {
|
|
|
391
411
|
const condition = joiSpec.whens[0]
|
|
392
412
|
schema.oneOf = []
|
|
393
413
|
if (condition.then) {
|
|
394
|
-
schema.oneOf.push(this.parse(condition.then))
|
|
414
|
+
schema.oneOf.push(this.parse(condition.then, definitions, level + 1))
|
|
395
415
|
}
|
|
396
416
|
if (condition.otherwise) {
|
|
397
|
-
schema.oneOf.push(this.parse(condition.otherwise))
|
|
417
|
+
schema.oneOf.push(this.parse(condition.otherwise, definitions, level + 1))
|
|
398
418
|
}
|
|
399
419
|
delete schema.type
|
|
400
420
|
return
|
|
@@ -420,6 +440,17 @@ class JoiJsonSchemaParser {
|
|
|
420
440
|
}
|
|
421
441
|
})
|
|
422
442
|
}
|
|
443
|
+
|
|
444
|
+
_setLinkFieldProperties(schema, joiSpec) {
|
|
445
|
+
if (schema.type !== 'link') {
|
|
446
|
+
return
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (_.get(joiSpec, 'link.ref.type') === 'local') {
|
|
450
|
+
schema.$ref = `${this._getLocalSchemaBasePath()}/${joiSpec.link.ref.path.join('/')}`
|
|
451
|
+
delete schema.type
|
|
452
|
+
}
|
|
453
|
+
}
|
|
423
454
|
}
|
|
424
455
|
|
|
425
456
|
module.exports = JoiJsonSchemaParser
|
package/lib/parsers/open-api.js
CHANGED
|
@@ -2,8 +2,10 @@ const _ = require('lodash')
|
|
|
2
2
|
const JoiJsonSchemaParser = require('./json-draft-04')
|
|
3
3
|
|
|
4
4
|
class JoiOpenApiSchemaParser extends JoiJsonSchemaParser {
|
|
5
|
-
parse(joiSpec) {
|
|
6
|
-
const
|
|
5
|
+
parse(joiSpec, definitions = {}, level = 0) {
|
|
6
|
+
const fullSchema = super.parse(joiSpec, definitions, level)
|
|
7
|
+
const schema = _.pick(fullSchema, [
|
|
8
|
+
'$ref',
|
|
7
9
|
'title',
|
|
8
10
|
'multipleOf',
|
|
9
11
|
'maximum',
|
|
@@ -47,9 +49,17 @@ class JoiOpenApiSchemaParser extends JoiJsonSchemaParser {
|
|
|
47
49
|
})
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
if (level === 0 && !_.isEmpty(definitions)) {
|
|
53
|
+
schema.schemas = definitions
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
return schema
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
_getLocalSchemaBasePath() {
|
|
60
|
+
return '#/components/schemas'
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
_setBasicProperties(fieldSchema, fieldDefn) {
|
|
54
64
|
super._setBasicProperties(fieldSchema, fieldDefn)
|
|
55
65
|
|