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 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-12` to control which version of joi obj to test
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 JoiOpenApiSchemaParser extends JoiJsonSchemaParser {
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 = JoiOpenApiSchemaParser
21
+ module.exports = JoiJsonDraftSchemaParser
@@ -1,9 +1,9 @@
1
1
  const _ = require('lodash')
2
2
  const JoiJsonSchemaParser = require('./json')
3
3
 
4
- class JoiOpenApiSchemaParser extends JoiJsonSchemaParser {
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 = JoiOpenApiSchemaParser
22
+ module.exports = JoiJsonDraftSchemaParser
@@ -10,8 +10,8 @@ class JoiJsonSchemaParser {
10
10
  this.allowUnknownFlagName = this._getAllowUnknownFlagName()
11
11
  }
12
12
 
13
- parse(joiSpec) {
14
- const schema = {}
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
- if (_.isEmpty(fieldDefn[this.enumFieldName])) {
100
- return undefined
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, this.parse.bind(this))
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
@@ -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 schema = _.pick(super.parse(joiSpec), [
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joi-to-json",
3
- "version": "2.2.3",
3
+ "version": "2.3.1",
4
4
  "description": "joi to JSON / OpenAPI Schema Converter",
5
5
  "main": "index.js",
6
6
  "repository": {