joi-to-json 2.2.3 → 2.3.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/README.md +7 -1
- package/index.js +3 -2
- package/lib/parsers/json-draft-04.js +6 -2
- package/lib/parsers/json-draft-2019-09.js +4 -4
- package/lib/parsers/json.js +52 -27
- 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
|
@@ -21,7 +21,7 @@ fs.readdirSync(parsersDir).forEach(file => {
|
|
|
21
21
|
}
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
function parse(joiObj, type = 'json') {
|
|
24
|
+
function parse(joiObj, type = 'json', definitions = {}) {
|
|
25
25
|
if (typeof joiObj.describe !== 'function') {
|
|
26
26
|
throw new Error('Not an joi object.')
|
|
27
27
|
}
|
|
@@ -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]
|
|
@@ -57,7 +58,7 @@ function parse(joiObj, type = 'json') {
|
|
|
57
58
|
throw new Error(`No parser is registered for ${type}`)
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
return new parser().parse(joiBaseSpec)
|
|
61
|
+
return new parser().parse(joiBaseSpec, definitions)
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
module.exports = parse
|
|
@@ -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,10 @@ 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
|
}
|
|
102
|
-
|
|
103
|
-
const enumList = _.filter(fieldDefn[this.enumFieldName], (item) => {
|
|
104
|
-
return !_.isEmpty(item)
|
|
105
|
-
})
|
|
106
|
-
return _.isEmpty(enumList) ? undefined : enumList
|
|
107
119
|
}
|
|
108
120
|
|
|
109
121
|
_getUnknown(joiSpec) {
|
|
@@ -139,7 +151,7 @@ class JoiJsonSchemaParser {
|
|
|
139
151
|
fieldSchema.format = 'binary'
|
|
140
152
|
}
|
|
141
153
|
|
|
142
|
-
_setObjectProperties(schema, joiSpec) {
|
|
154
|
+
_setObjectProperties(schema, joiSpec, definitions, level) {
|
|
143
155
|
if (schema.type !== 'object') {
|
|
144
156
|
return
|
|
145
157
|
}
|
|
@@ -150,7 +162,7 @@ class JoiJsonSchemaParser {
|
|
|
150
162
|
schema.additionalProperties = this._getUnknown(joiSpec)
|
|
151
163
|
|
|
152
164
|
_.map(joiSpec[this.childrenFieldName], (fieldDefn, key) => {
|
|
153
|
-
const fieldSchema = this.parse(fieldDefn)
|
|
165
|
+
const fieldSchema = this.parse(fieldDefn, definitions, level + 1)
|
|
154
166
|
if (this._isRequired(fieldDefn)) {
|
|
155
167
|
schema.required.push(key)
|
|
156
168
|
}
|
|
@@ -178,7 +190,7 @@ class JoiJsonSchemaParser {
|
|
|
178
190
|
schema.properties[patternObj.regex].additionalProperties = this._getUnknown(patternObj.rule)
|
|
179
191
|
|
|
180
192
|
_.each(childKeys, (ruleObj, key) => {
|
|
181
|
-
schema.properties[patternObj.regex].properties[key] = this.parse(ruleObj)
|
|
193
|
+
schema.properties[patternObj.regex].properties[key] = this.parse(ruleObj, definitions, level + 1)
|
|
182
194
|
|
|
183
195
|
if (this._isRequired(ruleObj)) {
|
|
184
196
|
schema.properties[patternObj.regex].required.push(key)
|
|
@@ -305,7 +317,7 @@ class JoiJsonSchemaParser {
|
|
|
305
317
|
})
|
|
306
318
|
}
|
|
307
319
|
|
|
308
|
-
_setArrayFieldProperties(fieldSchema, fieldDefn) {
|
|
320
|
+
_setArrayFieldProperties(fieldSchema, fieldDefn, definitions, level) {
|
|
309
321
|
if (fieldSchema.type !== 'array') {
|
|
310
322
|
return
|
|
311
323
|
}
|
|
@@ -339,10 +351,12 @@ class JoiJsonSchemaParser {
|
|
|
339
351
|
}
|
|
340
352
|
|
|
341
353
|
if (fieldDefn.items.length === 1) {
|
|
342
|
-
fieldSchema.items = this.parse(fieldDefn.items[0])
|
|
354
|
+
fieldSchema.items = this.parse(fieldDefn.items[0], definitions, level + 1)
|
|
343
355
|
} else {
|
|
344
356
|
fieldSchema.items = {
|
|
345
|
-
anyOf: _.map(fieldDefn.items,
|
|
357
|
+
anyOf: _.map(fieldDefn.items, (itemSchema) => {
|
|
358
|
+
return this.parse(itemSchema, definitions, level + 1)
|
|
359
|
+
})
|
|
346
360
|
}
|
|
347
361
|
}
|
|
348
362
|
}
|
|
@@ -363,7 +377,7 @@ class JoiJsonSchemaParser {
|
|
|
363
377
|
}
|
|
364
378
|
}
|
|
365
379
|
|
|
366
|
-
_setAlternativesProperties(schema, joiSpec) {
|
|
380
|
+
_setAlternativesProperties(schema, joiSpec, definitions, level) {
|
|
367
381
|
if (schema.type !== 'alternatives') {
|
|
368
382
|
return
|
|
369
383
|
}
|
|
@@ -372,23 +386,23 @@ class JoiJsonSchemaParser {
|
|
|
372
386
|
const match = joiSpec.matches[0]
|
|
373
387
|
if (match.switch) {
|
|
374
388
|
schema.oneOf = _.map(match.switch, (condition) => {
|
|
375
|
-
return this.parse(condition.then || condition.otherwise)
|
|
389
|
+
return this.parse(condition.then || condition.otherwise, definitions, level + 1)
|
|
376
390
|
})
|
|
377
391
|
} else if (match.then || match.otherwise) {
|
|
378
392
|
schema.oneOf = []
|
|
379
|
-
if (match.then) schema.oneOf.push(this.parse(match.then))
|
|
380
|
-
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))
|
|
381
395
|
}
|
|
382
396
|
} else {
|
|
383
397
|
schema.oneOf = _.map(joiSpec.matches, (match) => {
|
|
384
|
-
return this.parse(match.schema)
|
|
398
|
+
return this.parse(match.schema, definitions, level + 1)
|
|
385
399
|
})
|
|
386
400
|
}
|
|
387
401
|
|
|
388
402
|
delete schema.type
|
|
389
403
|
}
|
|
390
404
|
|
|
391
|
-
_setAnyProperties(schema, joiSpec) {
|
|
405
|
+
_setAnyProperties(schema, joiSpec, definitions, level) {
|
|
392
406
|
if (schema.type !== 'any') {
|
|
393
407
|
return
|
|
394
408
|
}
|
|
@@ -397,10 +411,10 @@ class JoiJsonSchemaParser {
|
|
|
397
411
|
const condition = joiSpec.whens[0]
|
|
398
412
|
schema.oneOf = []
|
|
399
413
|
if (condition.then) {
|
|
400
|
-
schema.oneOf.push(this.parse(condition.then))
|
|
414
|
+
schema.oneOf.push(this.parse(condition.then, definitions, level + 1))
|
|
401
415
|
}
|
|
402
416
|
if (condition.otherwise) {
|
|
403
|
-
schema.oneOf.push(this.parse(condition.otherwise))
|
|
417
|
+
schema.oneOf.push(this.parse(condition.otherwise, definitions, level + 1))
|
|
404
418
|
}
|
|
405
419
|
delete schema.type
|
|
406
420
|
return
|
|
@@ -426,6 +440,17 @@ class JoiJsonSchemaParser {
|
|
|
426
440
|
}
|
|
427
441
|
})
|
|
428
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
|
+
}
|
|
429
454
|
}
|
|
430
455
|
|
|
431
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
|
|