joi-to-json 2.2.4 → 2.3.2

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, definitions, level)
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
  }
@@ -135,7 +151,7 @@ class JoiJsonSchemaParser {
135
151
  fieldSchema.format = 'binary'
136
152
  }
137
153
 
138
- _setObjectProperties(schema, joiSpec) {
154
+ _setObjectProperties(schema, joiSpec, definitions, level) {
139
155
  if (schema.type !== 'object') {
140
156
  return
141
157
  }
@@ -146,7 +162,7 @@ class JoiJsonSchemaParser {
146
162
  schema.additionalProperties = this._getUnknown(joiSpec)
147
163
 
148
164
  _.map(joiSpec[this.childrenFieldName], (fieldDefn, key) => {
149
- const fieldSchema = this.parse(fieldDefn)
165
+ const fieldSchema = this.parse(fieldDefn, definitions, level + 1)
150
166
  if (this._isRequired(fieldDefn)) {
151
167
  schema.required.push(key)
152
168
  }
@@ -174,7 +190,7 @@ class JoiJsonSchemaParser {
174
190
  schema.properties[patternObj.regex].additionalProperties = this._getUnknown(patternObj.rule)
175
191
 
176
192
  _.each(childKeys, (ruleObj, key) => {
177
- schema.properties[patternObj.regex].properties[key] = this.parse(ruleObj)
193
+ schema.properties[patternObj.regex].properties[key] = this.parse(ruleObj, definitions, level + 1)
178
194
 
179
195
  if (this._isRequired(ruleObj)) {
180
196
  schema.properties[patternObj.regex].required.push(key)
@@ -301,7 +317,7 @@ class JoiJsonSchemaParser {
301
317
  })
302
318
  }
303
319
 
304
- _setArrayFieldProperties(fieldSchema, fieldDefn) {
320
+ _setArrayFieldProperties(fieldSchema, fieldDefn, definitions, level) {
305
321
  if (fieldSchema.type !== 'array') {
306
322
  return
307
323
  }
@@ -335,10 +351,12 @@ class JoiJsonSchemaParser {
335
351
  }
336
352
 
337
353
  if (fieldDefn.items.length === 1) {
338
- fieldSchema.items = this.parse(fieldDefn.items[0])
354
+ fieldSchema.items = this.parse(fieldDefn.items[0], definitions, level + 1)
339
355
  } else {
340
356
  fieldSchema.items = {
341
- anyOf: _.map(fieldDefn.items, this.parse.bind(this))
357
+ anyOf: _.map(fieldDefn.items, (itemSchema) => {
358
+ return this.parse(itemSchema, definitions, level + 1)
359
+ })
342
360
  }
343
361
  }
344
362
  }
@@ -359,7 +377,7 @@ class JoiJsonSchemaParser {
359
377
  }
360
378
  }
361
379
 
362
- _setAlternativesProperties(schema, joiSpec) {
380
+ _setAlternativesProperties(schema, joiSpec, definitions, level) {
363
381
  if (schema.type !== 'alternatives') {
364
382
  return
365
383
  }
@@ -368,23 +386,23 @@ class JoiJsonSchemaParser {
368
386
  const match = joiSpec.matches[0]
369
387
  if (match.switch) {
370
388
  schema.oneOf = _.map(match.switch, (condition) => {
371
- return this.parse(condition.then || condition.otherwise)
389
+ return this.parse(condition.then || condition.otherwise, definitions, level + 1)
372
390
  })
373
391
  } else if (match.then || match.otherwise) {
374
392
  schema.oneOf = []
375
- if (match.then) schema.oneOf.push(this.parse(match.then))
376
- 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))
377
395
  }
378
396
  } else {
379
397
  schema.oneOf = _.map(joiSpec.matches, (match) => {
380
- return this.parse(match.schema)
398
+ return this.parse(match.schema, definitions, level + 1)
381
399
  })
382
400
  }
383
401
 
384
402
  delete schema.type
385
403
  }
386
404
 
387
- _setAnyProperties(schema, joiSpec) {
405
+ _setAnyProperties(schema, joiSpec, definitions, level) {
388
406
  if (schema.type !== 'any') {
389
407
  return
390
408
  }
@@ -393,10 +411,10 @@ class JoiJsonSchemaParser {
393
411
  const condition = joiSpec.whens[0]
394
412
  schema.oneOf = []
395
413
  if (condition.then) {
396
- schema.oneOf.push(this.parse(condition.then))
414
+ schema.oneOf.push(this.parse(condition.then, definitions, level + 1))
397
415
  }
398
416
  if (condition.otherwise) {
399
- schema.oneOf.push(this.parse(condition.otherwise))
417
+ schema.oneOf.push(this.parse(condition.otherwise, definitions, level + 1))
400
418
  }
401
419
  delete schema.type
402
420
  return
@@ -422,6 +440,21 @@ class JoiJsonSchemaParser {
422
440
  }
423
441
  })
424
442
  }
443
+
444
+ _setLinkFieldProperties(schema, joiSpec, definitions, level) {
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
+
454
+ if (!_.isEmpty(joiSpec.shared)) {
455
+ this.parse(joiSpec.shared[0], definitions, level)
456
+ }
457
+ }
425
458
  }
426
459
 
427
460
  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.4",
3
+ "version": "2.3.2",
4
4
  "description": "joi to JSON / OpenAPI Schema Converter",
5
5
  "main": "index.js",
6
6
  "repository": {