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 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
@@ -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 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,18 @@ 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
  }
119
+ }
102
120
 
103
- const enumList = _.filter(fieldDefn[this.enumFieldName], (item) => {
104
- return !_.isEmpty(item)
105
- })
106
- return _.isEmpty(enumList) ? undefined : enumList
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 = _.get(joiSpec, `${this.optionsFieldName}.allowUnknown`, false)
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, this.parse.bind(this))
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
@@ -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.2",
3
+ "version": "2.3.0",
4
4
  "description": "joi to JSON / OpenAPI Schema Converter",
5
5
  "main": "index.js",
6
6
  "repository": {