serverless-openapi-documenter 0.0.52 → 0.0.60
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/package.json +1 -1
- package/src/definitionGenerator.js +46 -105
- package/src/schemaHandler.js +189 -0
- package/test/models/models/models-alt.json +17 -0
- package/test/models/models/models.json +20 -0
- package/test/models/models/modelsList-alt.json +17 -0
- package/test/models/models/modelsList.json +20 -0
- package/test/unit/definitionGenerator.spec.js +25 -502
- package/test/unit/schemaHandler.spec.js +700 -0
package/package.json
CHANGED
|
@@ -4,9 +4,8 @@ const path = require('path')
|
|
|
4
4
|
|
|
5
5
|
const { v4: uuid } = require('uuid')
|
|
6
6
|
const validator = require('oas-validator');
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const isEqual = require('lodash.isequal')
|
|
7
|
+
|
|
8
|
+
const SchemaHandler = require('./schemaHandler')
|
|
10
9
|
|
|
11
10
|
class DefinitionGenerator {
|
|
12
11
|
constructor(serverless, options = {}) {
|
|
@@ -25,8 +24,13 @@ class DefinitionGenerator {
|
|
|
25
24
|
|
|
26
25
|
this.openAPI = {
|
|
27
26
|
openapi: this.version,
|
|
27
|
+
components: {
|
|
28
|
+
schemas: {}
|
|
29
|
+
}
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
this.schemaHandler = new SchemaHandler(serverless, this.openAPI)
|
|
33
|
+
|
|
30
34
|
this.operationIds = []
|
|
31
35
|
this.schemaIDs = []
|
|
32
36
|
|
|
@@ -64,6 +68,11 @@ class DefinitionGenerator {
|
|
|
64
68
|
async parse() {
|
|
65
69
|
this.createInfo()
|
|
66
70
|
|
|
71
|
+
await this.schemaHandler.addModelsToOpenAPI()
|
|
72
|
+
.catch(err => {
|
|
73
|
+
throw err
|
|
74
|
+
})
|
|
75
|
+
|
|
67
76
|
if (this.serverless.service.custom.documentation.securitySchemes) {
|
|
68
77
|
this.createSecuritySchemes(this.serverless.service.custom.documentation.securitySchemes)
|
|
69
78
|
|
|
@@ -79,7 +88,7 @@ class DefinitionGenerator {
|
|
|
79
88
|
|
|
80
89
|
if (this.serverless.service.custom.documentation.servers) {
|
|
81
90
|
const servers = this.createServers(this.serverless.service.custom.documentation.servers)
|
|
82
|
-
Object.assign(this.openAPI, {servers: servers})
|
|
91
|
+
Object.assign(this.openAPI, { servers: servers })
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
if (this.serverless.service.custom.documentation.tags) {
|
|
@@ -88,7 +97,7 @@ class DefinitionGenerator {
|
|
|
88
97
|
|
|
89
98
|
if (this.serverless.service.custom.documentation.externalDocumentation) {
|
|
90
99
|
const extDoc = this.createExternalDocumentation(this.serverless.service.custom.documentation.externalDocumentation)
|
|
91
|
-
Object.assign(this.openAPI, {externalDocs: extDoc})
|
|
100
|
+
Object.assign(this.openAPI, { externalDocs: extDoc })
|
|
92
101
|
}
|
|
93
102
|
}
|
|
94
103
|
|
|
@@ -113,7 +122,7 @@ class DefinitionGenerator {
|
|
|
113
122
|
contactObj.url = documentation.contact.url
|
|
114
123
|
|
|
115
124
|
contactObj.email = documentation.contact.email || ''
|
|
116
|
-
Object.assign(info, {contact: contactObj})
|
|
125
|
+
Object.assign(info, { contact: contactObj })
|
|
117
126
|
}
|
|
118
127
|
|
|
119
128
|
if (documentation.license && documentation.license.name) {
|
|
@@ -123,16 +132,16 @@ class DefinitionGenerator {
|
|
|
123
132
|
if (documentation.license.url)
|
|
124
133
|
licenseObj.url = documentation.license.url || ''
|
|
125
134
|
|
|
126
|
-
Object.assign(info, {license: licenseObj})
|
|
135
|
+
Object.assign(info, { license: licenseObj })
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
for (const key of Object.keys(documentation)) {
|
|
130
139
|
if (/^[x\-]/i.test(key)) {
|
|
131
|
-
Object.assign(info, {[key]: documentation[key]})
|
|
140
|
+
Object.assign(info, { [key]: documentation[key] })
|
|
132
141
|
}
|
|
133
142
|
}
|
|
134
143
|
|
|
135
|
-
Object.assign(this.openAPI, {info})
|
|
144
|
+
Object.assign(this.openAPI, { info })
|
|
136
145
|
}
|
|
137
146
|
|
|
138
147
|
async createPaths() {
|
|
@@ -173,18 +182,18 @@ class DefinitionGenerator {
|
|
|
173
182
|
let slashPath = (event?.http?.path || event.httpApi?.path) ?? '/'
|
|
174
183
|
const pathStart = new RegExp(/^\//, 'g')
|
|
175
184
|
if (pathStart.test(slashPath) === false) {
|
|
176
|
-
slashPath = `/${(event?.http?.path||event.httpApi?.path)?? ''}`
|
|
185
|
+
slashPath = `/${(event?.http?.path || event.httpApi?.path) ?? ''}`
|
|
177
186
|
}
|
|
178
187
|
|
|
179
188
|
if (paths[slashPath]) {
|
|
180
189
|
Object.assign(paths[slashPath], path);
|
|
181
190
|
} else {
|
|
182
|
-
Object.assign(paths, {[slashPath]: path});
|
|
191
|
+
Object.assign(paths, { [slashPath]: path });
|
|
183
192
|
}
|
|
184
193
|
}
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
|
-
Object.assign(this.openAPI, {paths})
|
|
196
|
+
Object.assign(this.openAPI, { paths })
|
|
188
197
|
}
|
|
189
198
|
|
|
190
199
|
createServers(servers) {
|
|
@@ -227,7 +236,7 @@ class DefinitionGenerator {
|
|
|
227
236
|
}
|
|
228
237
|
|
|
229
238
|
createExternalDocumentation(docs) {
|
|
230
|
-
return {...docs}
|
|
239
|
+
return { ...docs }
|
|
231
240
|
// const documentation = this.serverless.service.custom.documentation
|
|
232
241
|
// if (documentation.externalDocumentation) {
|
|
233
242
|
// // Object.assign(this.openAPI, {externalDocs: {...documentation.externalDocumentation}})
|
|
@@ -251,7 +260,7 @@ class DefinitionGenerator {
|
|
|
251
260
|
}
|
|
252
261
|
tags.push(obj)
|
|
253
262
|
}
|
|
254
|
-
Object.assign(this.openAPI, {tags: tags})
|
|
263
|
+
Object.assign(this.openAPI, { tags: tags })
|
|
255
264
|
}
|
|
256
265
|
|
|
257
266
|
async createOperationObject(method, documentation, name = uuid()) {
|
|
@@ -323,7 +332,7 @@ class DefinitionGenerator {
|
|
|
323
332
|
obj.servers = servers
|
|
324
333
|
}
|
|
325
334
|
|
|
326
|
-
return {[method.toLowerCase()]: obj}
|
|
335
|
+
return { [method.toLowerCase()]: obj }
|
|
327
336
|
}
|
|
328
337
|
|
|
329
338
|
async createResponses(documentation) {
|
|
@@ -360,10 +369,11 @@ class DefinitionGenerator {
|
|
|
360
369
|
}
|
|
361
370
|
}
|
|
362
371
|
} else {
|
|
363
|
-
|
|
372
|
+
if (Object.keys(corsHeaders).length)
|
|
373
|
+
obj.headers = corsHeaders
|
|
364
374
|
}
|
|
365
375
|
|
|
366
|
-
Object.assign(responses,{[response.statusCode]: obj})
|
|
376
|
+
Object.assign(responses, { [response.statusCode]: obj })
|
|
367
377
|
}
|
|
368
378
|
|
|
369
379
|
return responses
|
|
@@ -380,7 +390,7 @@ class DefinitionGenerator {
|
|
|
380
390
|
const newHeaders = {}
|
|
381
391
|
for (const key of Object.keys(this.DEFAULT_CORS_HEADERS)) {
|
|
382
392
|
if (key === 'Access-Control-Allow-Credentials' &&
|
|
383
|
-
this.currentEvent.cors.allowCredentials === undefined || this.currentEvent.cors?.allowCredentials === false) {
|
|
393
|
+
(this.currentEvent.cors.allowCredentials === undefined || this.currentEvent.cors?.allowCredentials === false)) {
|
|
384
394
|
continue
|
|
385
395
|
}
|
|
386
396
|
|
|
@@ -394,7 +404,7 @@ class DefinitionGenerator {
|
|
|
394
404
|
}
|
|
395
405
|
}
|
|
396
406
|
|
|
397
|
-
Object.assign(newHeaders, {[key]: obj})
|
|
407
|
+
Object.assign(newHeaders, { [key]: obj })
|
|
398
408
|
}
|
|
399
409
|
|
|
400
410
|
headers = await this.createResponseHeaders(newHeaders)
|
|
@@ -414,7 +424,7 @@ class DefinitionGenerator {
|
|
|
414
424
|
newHeader.description = headers[header].description || ''
|
|
415
425
|
|
|
416
426
|
if (headers[header].schema) {
|
|
417
|
-
const schemaRef = await this.
|
|
427
|
+
const schemaRef = await this.schemaHandler.createSchema(header, headers[header].schema)
|
|
418
428
|
.catch(err => {
|
|
419
429
|
throw err
|
|
420
430
|
})
|
|
@@ -423,7 +433,7 @@ class DefinitionGenerator {
|
|
|
423
433
|
}
|
|
424
434
|
}
|
|
425
435
|
|
|
426
|
-
Object.assign(obj, {[header]: newHeader})
|
|
436
|
+
Object.assign(obj, { [header]: newHeader })
|
|
427
437
|
}
|
|
428
438
|
|
|
429
439
|
return obj
|
|
@@ -445,7 +455,7 @@ class DefinitionGenerator {
|
|
|
445
455
|
|
|
446
456
|
async createMediaTypeObject(models, type) {
|
|
447
457
|
const mediaTypeObj = {}
|
|
448
|
-
for (const mediaTypeDocumentation of this.
|
|
458
|
+
for (const mediaTypeDocumentation of this.schemaHandler.models) {
|
|
449
459
|
if (models === undefined || models === null) {
|
|
450
460
|
throw new Error(`${this.currentFunctionName} is missing a Response Model for statusCode ${this.currentStatusCode}`)
|
|
451
461
|
}
|
|
@@ -477,15 +487,16 @@ class DefinitionGenerator {
|
|
|
477
487
|
schema = mediaTypeDocumentation.schema
|
|
478
488
|
}
|
|
479
489
|
|
|
480
|
-
const schemaRef = await this.
|
|
490
|
+
const schemaRef = await this.schemaHandler.createSchema(mediaTypeDocumentation.name)
|
|
481
491
|
.catch(err => {
|
|
482
492
|
throw err
|
|
483
493
|
})
|
|
494
|
+
|
|
484
495
|
obj.schema = {
|
|
485
496
|
$ref: schemaRef
|
|
486
497
|
}
|
|
487
498
|
|
|
488
|
-
Object.assign(mediaTypeObj, {[contentKey]: obj})
|
|
499
|
+
Object.assign(mediaTypeObj, { [contentKey]: obj })
|
|
489
500
|
}
|
|
490
501
|
}
|
|
491
502
|
return mediaTypeObj
|
|
@@ -525,7 +536,7 @@ class DefinitionGenerator {
|
|
|
525
536
|
obj.examples = this.createExamples(param.examples)
|
|
526
537
|
|
|
527
538
|
if (param.schema) {
|
|
528
|
-
const schemaRef = await this.
|
|
539
|
+
const schemaRef = await this.schemaHandler.createSchema(param.name, param.schema)
|
|
529
540
|
.catch(err => {
|
|
530
541
|
throw err
|
|
531
542
|
})
|
|
@@ -539,76 +550,6 @@ class DefinitionGenerator {
|
|
|
539
550
|
return params;
|
|
540
551
|
}
|
|
541
552
|
|
|
542
|
-
async dereferenceSchema(schema) {
|
|
543
|
-
let originalSchema = await $RefParser.bundle(schema, this.refParserOptions)
|
|
544
|
-
.catch(err => {
|
|
545
|
-
console.error(err)
|
|
546
|
-
throw err
|
|
547
|
-
})
|
|
548
|
-
|
|
549
|
-
let deReferencedSchema = await $RefParser.dereference(originalSchema, this.refParserOptions)
|
|
550
|
-
.catch(err => {
|
|
551
|
-
console.error(err)
|
|
552
|
-
throw err
|
|
553
|
-
})
|
|
554
|
-
|
|
555
|
-
// deal with schemas that have been de-referenced poorly: naive
|
|
556
|
-
if (deReferencedSchema?.$ref === '#') {
|
|
557
|
-
const oldRef = originalSchema.$ref
|
|
558
|
-
const path = oldRef.split('/')
|
|
559
|
-
|
|
560
|
-
const pathTitle = path[path.length-1]
|
|
561
|
-
const referencedProperties = deReferencedSchema.definitions[pathTitle]
|
|
562
|
-
|
|
563
|
-
Object.assign(deReferencedSchema, {...referencedProperties})
|
|
564
|
-
|
|
565
|
-
delete deReferencedSchema.$ref
|
|
566
|
-
deReferencedSchema = await this.dereferenceSchema(deReferencedSchema)
|
|
567
|
-
.catch((err) => {
|
|
568
|
-
throw err
|
|
569
|
-
})
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
return deReferencedSchema
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
async schemaCreator(schema, name) {
|
|
576
|
-
let schemaName = name
|
|
577
|
-
let finalName = schemaName
|
|
578
|
-
const dereferencedSchema = await this.dereferenceSchema(schema)
|
|
579
|
-
.catch(err => {
|
|
580
|
-
console.error(err)
|
|
581
|
-
throw err
|
|
582
|
-
})
|
|
583
|
-
|
|
584
|
-
const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, schemaName)
|
|
585
|
-
|
|
586
|
-
for (const convertedSchemaName of Object.keys(convertedSchemas.schemas)) {
|
|
587
|
-
const convertedSchema = convertedSchemas.schemas[convertedSchemaName]
|
|
588
|
-
if (this.existsInComponents(convertedSchemaName)) {
|
|
589
|
-
if (this.isTheSameSchema(convertedSchema, convertedSchemaName) === false) {
|
|
590
|
-
if (convertedSchemaName === schemaName) {
|
|
591
|
-
finalName = `${schemaName}-${uuid()}`
|
|
592
|
-
this.addToComponents(this.componentTypes.schemas, convertedSchema, finalName)
|
|
593
|
-
} else
|
|
594
|
-
this.addToComponents(this.componentTypes.schemas, convertedSchema, convertedSchemaName)
|
|
595
|
-
}
|
|
596
|
-
} else {
|
|
597
|
-
this.addToComponents(this.componentTypes.schemas, convertedSchema, convertedSchemaName)
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
return `#/components/schemas/${finalName}`
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
existsInComponents(name) {
|
|
605
|
-
return Boolean(this.openAPI?.components?.schemas?.[name])
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
isTheSameSchema(schema, otherSchemaName) {
|
|
609
|
-
return isEqual(schema, this.openAPI.components.schemas[otherSchemaName])
|
|
610
|
-
}
|
|
611
|
-
|
|
612
553
|
addToComponents(type, schema, name) {
|
|
613
554
|
const schemaObj = {
|
|
614
555
|
[name]: schema
|
|
@@ -618,7 +559,7 @@ class DefinitionGenerator {
|
|
|
618
559
|
if (this.openAPI.components[type]) {
|
|
619
560
|
Object.assign(this.openAPI.components[type], schemaObj)
|
|
620
561
|
} else {
|
|
621
|
-
Object.assign(this.openAPI.components, {[type]: schemaObj})
|
|
562
|
+
Object.assign(this.openAPI.components, { [type]: schemaObj })
|
|
622
563
|
}
|
|
623
564
|
} else {
|
|
624
565
|
const components = {
|
|
@@ -639,30 +580,30 @@ class DefinitionGenerator {
|
|
|
639
580
|
if (securityScheme.description)
|
|
640
581
|
schema.description = securityScheme.description
|
|
641
582
|
|
|
642
|
-
switch(securityScheme.type.toLowerCase()) {
|
|
583
|
+
switch (securityScheme.type.toLowerCase()) {
|
|
643
584
|
case 'apikey':
|
|
644
585
|
const apiKeyScheme = this.createAPIKeyScheme(securityScheme)
|
|
645
586
|
schema.type = 'apiKey'
|
|
646
587
|
Object.assign(schema, apiKeyScheme)
|
|
647
|
-
|
|
588
|
+
break;
|
|
648
589
|
|
|
649
590
|
case 'http':
|
|
650
591
|
const HTTPScheme = this.createHTTPScheme(securityScheme)
|
|
651
592
|
schema.type = 'http'
|
|
652
593
|
Object.assign(schema, HTTPScheme)
|
|
653
|
-
|
|
594
|
+
break;
|
|
654
595
|
|
|
655
596
|
case 'openidconnect':
|
|
656
597
|
const openIdConnectScheme = this.createOpenIDConnectScheme(securityScheme)
|
|
657
598
|
schema.type = 'openIdConnect'
|
|
658
599
|
Object.assign(schema, openIdConnectScheme)
|
|
659
|
-
|
|
600
|
+
break;
|
|
660
601
|
|
|
661
602
|
case 'oauth2':
|
|
662
603
|
const oAuth2Scheme = this.createOAuth2Scheme(securityScheme)
|
|
663
604
|
schema.type = 'oauth2'
|
|
664
605
|
Object.assign(schema, oAuth2Scheme)
|
|
665
|
-
|
|
606
|
+
break;
|
|
666
607
|
}
|
|
667
608
|
|
|
668
609
|
this.addToComponents(this.componentTypes.securitySchemes, schema, scheme)
|
|
@@ -712,7 +653,7 @@ class DefinitionGenerator {
|
|
|
712
653
|
const schema = {}
|
|
713
654
|
if (securitySchema.flows) {
|
|
714
655
|
const flows = this.createOAuthFlows(securitySchema.flows)
|
|
715
|
-
Object.assign(schema, {flows: flows})
|
|
656
|
+
Object.assign(schema, { flows: flows })
|
|
716
657
|
} else
|
|
717
658
|
throw new Error('Security Scheme for "oauth2" requires flows')
|
|
718
659
|
|
|
@@ -744,7 +685,7 @@ class DefinitionGenerator {
|
|
|
744
685
|
else
|
|
745
686
|
throw new Error(`oAuth2 ${flow} flow requires scopes`)
|
|
746
687
|
|
|
747
|
-
Object.assign(obj, {[flow]: schema})
|
|
688
|
+
Object.assign(obj, { [flow]: schema })
|
|
748
689
|
}
|
|
749
690
|
return obj
|
|
750
691
|
}
|
|
@@ -752,8 +693,8 @@ class DefinitionGenerator {
|
|
|
752
693
|
createExamples(examples) {
|
|
753
694
|
const examplesObj = {}
|
|
754
695
|
|
|
755
|
-
for(const example of examples) {
|
|
756
|
-
Object.assign(examplesObj, {[example.name]: example})
|
|
696
|
+
for (const example of examples) {
|
|
697
|
+
Object.assign(examplesObj, { [example.name]: example })
|
|
757
698
|
delete examplesObj[example.name].name
|
|
758
699
|
}
|
|
759
700
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const $RefParser = require("@apidevtools/json-schema-ref-parser")
|
|
6
|
+
const SchemaConvertor = require('json-schema-for-openapi')
|
|
7
|
+
const isEqual = require('lodash.isequal')
|
|
8
|
+
const { v4: uuid } = require('uuid')
|
|
9
|
+
|
|
10
|
+
class SchemaHandler {
|
|
11
|
+
constructor(serverless, openAPI) {
|
|
12
|
+
this.documentation = serverless.service.custom.documentation
|
|
13
|
+
this.openAPI = openAPI
|
|
14
|
+
|
|
15
|
+
this.modelReferences = {}
|
|
16
|
+
|
|
17
|
+
this.__standardiseModels()
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
this.refParserOptions = require(path.resolve('options', 'ref-parser.js'))
|
|
21
|
+
} catch (err) {
|
|
22
|
+
this.refParserOptions = {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Standardises the models to a specific format
|
|
28
|
+
*/
|
|
29
|
+
__standardiseModels() {
|
|
30
|
+
const standardModel = (model) => {
|
|
31
|
+
if (model.schema) {
|
|
32
|
+
return model
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const contentType = Object.keys(model.content)[0]
|
|
36
|
+
model.contentType = contentType
|
|
37
|
+
model.schema = model.content[contentType].schema
|
|
38
|
+
|
|
39
|
+
return model
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const standardisedModels = this.documentation?.models?.map(standardModel) || []
|
|
43
|
+
const standardisedModelsList = this.documentation?.modelsList?.map(standardModel) || []
|
|
44
|
+
|
|
45
|
+
this.models = standardisedModels.length ? standardisedModels.concat(standardisedModelsList) : standardisedModelsList
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async addModelsToOpenAPI() {
|
|
49
|
+
for (const model of this.models) {
|
|
50
|
+
const modelName = model.name
|
|
51
|
+
const modelSchema = model.schema
|
|
52
|
+
|
|
53
|
+
const dereferencedSchema = await this.__dereferenceSchema(modelSchema)
|
|
54
|
+
.catch(err => {
|
|
55
|
+
if(err.errors) {
|
|
56
|
+
for (const error of err?.errors) {
|
|
57
|
+
if (error.message.includes('HTTP ERROR')) {
|
|
58
|
+
throw err
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return modelSchema
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, modelName)
|
|
66
|
+
|
|
67
|
+
for (const [schemaName, schemaValue] of Object.entries(convertedSchemas.schemas)) {
|
|
68
|
+
if (schemaName === modelName) {
|
|
69
|
+
this.modelReferences[schemaName] = `#/components/schemas/${modelName}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.__addToComponents('schemas', schemaValue, schemaName)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async createSchema(name, schema) {
|
|
78
|
+
let originalName = name;
|
|
79
|
+
let finalName = name;
|
|
80
|
+
|
|
81
|
+
if (this.modelReferences[name] && schema === undefined) {
|
|
82
|
+
return this.modelReferences[name]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const dereferencedSchema = await this.__dereferenceSchema(schema)
|
|
86
|
+
.catch(err => {
|
|
87
|
+
throw err
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, name)
|
|
91
|
+
|
|
92
|
+
for (const [schemaName, schemaValue] of Object.entries(convertedSchemas.schemas)) {
|
|
93
|
+
if (this.__existsInComponents(schemaName)) {
|
|
94
|
+
if (this.__isTheSameSchema(schemaValue, schemaName) === false) {
|
|
95
|
+
if (schemaName === originalName) {
|
|
96
|
+
finalName = `${schemaName}-${uuid()}`
|
|
97
|
+
this.__addToComponents('schemas', schemaValue, finalName)
|
|
98
|
+
} else {
|
|
99
|
+
this.__addToComponents('schemas', schemaValue, schemaName)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
this.__addToComponents('schemas', schemaValue, schemaName)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return `#/components/schemas/${finalName}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async __dereferenceSchema(schema) {
|
|
111
|
+
const bundledSchema = await $RefParser.bundle(schema, this.refParserOptions)
|
|
112
|
+
.catch(err => {
|
|
113
|
+
throw err
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
let deReferencedSchema = await $RefParser.dereference(bundledSchema, this.refParserOptions)
|
|
117
|
+
.catch(err => {
|
|
118
|
+
throw err
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// deal with schemas that have been de-referenced poorly: naive
|
|
122
|
+
if (deReferencedSchema?.$ref === '#') {
|
|
123
|
+
const oldRef = bundledSchema.$ref
|
|
124
|
+
const path = oldRef.split('/')
|
|
125
|
+
|
|
126
|
+
const pathTitle = path[path.length - 1]
|
|
127
|
+
const referencedProperties = deReferencedSchema.definitions[pathTitle]
|
|
128
|
+
|
|
129
|
+
Object.assign(deReferencedSchema, { ...referencedProperties })
|
|
130
|
+
|
|
131
|
+
delete deReferencedSchema.$ref
|
|
132
|
+
deReferencedSchema = await this.__dereferenceSchema(deReferencedSchema)
|
|
133
|
+
.catch((err) => {
|
|
134
|
+
throw err
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return deReferencedSchema
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @function existsInComponents
|
|
143
|
+
* @param {string} name - The name of the Schema
|
|
144
|
+
* @returns {boolean} Whether it exists in components already
|
|
145
|
+
*/
|
|
146
|
+
__existsInComponents(name) {
|
|
147
|
+
return Boolean(this.openAPI?.components?.schemas?.[name])
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @function isTheSameSchema
|
|
152
|
+
* @param {object} schema - The schema value
|
|
153
|
+
* @param {string} otherSchemaName - The name of the schema
|
|
154
|
+
* @returns {boolean} Whether the schema provided is the same one as in components already
|
|
155
|
+
*/
|
|
156
|
+
__isTheSameSchema(schema, otherSchemaName) {
|
|
157
|
+
return isEqual(schema, this.openAPI.components.schemas[otherSchemaName])
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @function addToComponents
|
|
162
|
+
* @param {string} type - The component type
|
|
163
|
+
* @param {object} schema - The schema
|
|
164
|
+
* @param {string} name - The name of the schema
|
|
165
|
+
*/
|
|
166
|
+
__addToComponents(type, schema, name) {
|
|
167
|
+
const schemaObj = {
|
|
168
|
+
[name]: schema
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (this.openAPI?.components) {
|
|
172
|
+
if (this.openAPI.components[type]) {
|
|
173
|
+
Object.assign(this.openAPI.components[type], schemaObj)
|
|
174
|
+
} else {
|
|
175
|
+
Object.assign(this.openAPI.components, { [type]: schemaObj })
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
const components = {
|
|
179
|
+
components: {
|
|
180
|
+
[type]: schemaObj
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
Object.assign(this.openAPI, components)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = SchemaHandler;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"models": [
|
|
3
|
+
{
|
|
4
|
+
"name": "ErrorResponse",
|
|
5
|
+
"description": "The Error Response",
|
|
6
|
+
"content": {
|
|
7
|
+
"application/json": {
|
|
8
|
+
"schema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"error": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"modelsList": [
|
|
3
|
+
{
|
|
4
|
+
"name": "ErrorResponse",
|
|
5
|
+
"description": "The Error Response",
|
|
6
|
+
"content": {
|
|
7
|
+
"application/json": {
|
|
8
|
+
"schema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"error": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|