serverless-openapi-documenter 0.0.5 → 0.0.8
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 +106 -9
- package/package.json +6 -2
- package/src/definitionGenerator.js +155 -80
- package/src/openAPIGenerator.js +14 -4
- package/test/serverless 2/serverless.yml +8 -0
package/README.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# OpenAPI Generator for serverless
|
|
2
2
|
|
|
3
|
+
<p>
|
|
4
|
+
<a href="https://www.serverless.com">
|
|
5
|
+
<img src="http://public.serverless.com/badges/v3.svg">
|
|
6
|
+
</a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/serverless-openapi-documenter">
|
|
8
|
+
<img src="https://img.shields.io/npm/v/serverless-openapi-documenter.svg?style=flat-square">
|
|
9
|
+
</a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
3
12
|
This will generate an OpenAPI V3 (up to v3.0.3) file for you from your serverless file. It can optionally generate a Postman Collection V2 from the OpenAPI file for you too.
|
|
4
13
|
|
|
5
14
|
Originally based off of: https://github.com/temando/serverless-openapi-documentation
|
|
@@ -47,13 +56,17 @@ Options:
|
|
|
47
56
|
| info.version | custom.documentation.version OR random v4 uuid if not provided |
|
|
48
57
|
| externalDocs.description | custom.documentation.externalDocumentation.description |
|
|
49
58
|
| externalDocs.url | custom.documentation.externalDocumentation.url |
|
|
50
|
-
| servers[].description | custom.documentation.servers.description
|
|
51
|
-
| servers[].url | custom.documentation.servers.url
|
|
59
|
+
| servers[].description | custom.documentation.servers.description |
|
|
60
|
+
| servers[].url | custom.documentation.servers.url |
|
|
61
|
+
| tags[].name | custom.documentation.tags.name |
|
|
62
|
+
| tags[].description | custom.documentation.tags.description |
|
|
63
|
+
| tags[].externalDocs.url | custom.documentation.tags.externalDocumentation.url |
|
|
64
|
+
| tags[].externalDocs.description | custom.documentation.tags.externalDocumentation.description |
|
|
52
65
|
| path[path] | functions.functions.events.[http OR httpApi].path |
|
|
53
66
|
| path[path].summary | functions.functions.summary |
|
|
54
67
|
| path[path].description | functions.functions.description |
|
|
55
|
-
| path[path].servers[].description | functions.functions.servers.description
|
|
56
|
-
| path[path].servers[].url | functions.functions.servers.url
|
|
68
|
+
| path[path].servers[].description | functions.functions.servers.description |
|
|
69
|
+
| path[path].servers[].url | functions.functions.servers.url |
|
|
57
70
|
| path[path].[operation] | functions.functions.[http OR httpApi].method |
|
|
58
71
|
| path[path].[operation].summary | functions.functions.[http OR httpApi].documentation.summary |
|
|
59
72
|
| path[path].[operation].description | functions.functions.[http OR httpApi].documentation.description |
|
|
@@ -106,6 +119,12 @@ custom:
|
|
|
106
119
|
servers:
|
|
107
120
|
url: https://example.com
|
|
108
121
|
description: The server
|
|
122
|
+
tags:
|
|
123
|
+
- name: tag1
|
|
124
|
+
description: this is a tag
|
|
125
|
+
externalDocumentation:
|
|
126
|
+
url: https://npmjs.com
|
|
127
|
+
description: A link to npm
|
|
109
128
|
```
|
|
110
129
|
|
|
111
130
|
These configurations can be quite verbose; you can separate it out into it's own file, such as `serverless.doc.yml` as below:
|
|
@@ -134,7 +153,7 @@ The *required* directives for the models section are as follow:
|
|
|
134
153
|
* `name`: the name of the schema
|
|
135
154
|
* `description`: a description of the schema
|
|
136
155
|
* `contentType`: the content type of the described request/response (ie. `application/json` or `application/xml`).
|
|
137
|
-
* `schema`: The JSON Schema ([website](http://json-schema.org/)) that describes the model. You can either use inline `YAML` to define these
|
|
156
|
+
* `schema`: The JSON Schema ([website](http://json-schema.org/)) that describes the model. You can either use inline `YAML` to define these or use either an external file schema that serverless will resolve (as below), or a reference to an externally hosted schema that will be attempted to be resolved.
|
|
138
157
|
|
|
139
158
|
```yml
|
|
140
159
|
custom:
|
|
@@ -195,6 +214,8 @@ functions:
|
|
|
195
214
|
documentation:
|
|
196
215
|
summary: "Create User"
|
|
197
216
|
description: "Creates a user and then sends a generated password email"
|
|
217
|
+
tags:
|
|
218
|
+
- tag1
|
|
198
219
|
externalDocumentation:
|
|
199
220
|
url: https://bing.com
|
|
200
221
|
description: A link to bing
|
|
@@ -241,7 +262,7 @@ Query parameters can be described as follow:
|
|
|
241
262
|
* `name`: the name of the query variable
|
|
242
263
|
* `description`: a description of the query variable
|
|
243
264
|
* `required`: whether the query parameter is mandatory (boolean)
|
|
244
|
-
* `schema`: JSON schema (inline or
|
|
265
|
+
* `schema`: JSON schema (inline, file or externally hosted)
|
|
245
266
|
|
|
246
267
|
```yml
|
|
247
268
|
queryParams:
|
|
@@ -258,7 +279,7 @@ Path parameters can be described as follow:
|
|
|
258
279
|
|
|
259
280
|
* `name`: the name of the query variable
|
|
260
281
|
* `description`: a description of the query variable
|
|
261
|
-
* `schema`: JSON schema (inline or
|
|
282
|
+
* `schema`: JSON schema (inline, file or externally hosted)
|
|
262
283
|
|
|
263
284
|
```yml
|
|
264
285
|
pathParams:
|
|
@@ -275,7 +296,7 @@ Cookie parameters can be described as follow:
|
|
|
275
296
|
* `name`: the name of the query variable
|
|
276
297
|
* `description`: a description of the query variable
|
|
277
298
|
* `required`: whether the query parameter is mandatory (boolean)
|
|
278
|
-
* `schema`: JSON schema (inline or
|
|
299
|
+
* `schema`: JSON schema (inline, file or externally hosted)
|
|
279
300
|
|
|
280
301
|
```yml
|
|
281
302
|
cookieParams:
|
|
@@ -333,7 +354,7 @@ The attributes for a header are as follow:
|
|
|
333
354
|
|
|
334
355
|
* `name`: the name of the HTTP Header
|
|
335
356
|
* `description`: a description of the HTTP Header
|
|
336
|
-
* `schema`: JSON schema (inline or
|
|
357
|
+
* `schema`: JSON schema (inline, file or externally hosted)
|
|
337
358
|
|
|
338
359
|
```yml
|
|
339
360
|
responseHeaders:
|
|
@@ -352,6 +373,82 @@ requestHeaders:
|
|
|
352
373
|
|
|
353
374
|
Please view the example [serverless.yml](test/serverless\ 2/serverless.yml).
|
|
354
375
|
|
|
376
|
+
## Notes on schemas
|
|
377
|
+
|
|
378
|
+
Schemas can be either: inline, in file or externally hosted. If they're inline or in file, the plugin will attempt to normalise the schema to [OpenAPI 3.0.X specification](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#schemaObject).
|
|
379
|
+
|
|
380
|
+
If they exist as an external reference, for instance:
|
|
381
|
+
|
|
382
|
+
```yaml
|
|
383
|
+
schema: https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/bettercodehub.json
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
We use the plugin [JSON Schema $Ref Parser](https://apitools.dev/json-schema-ref-parser/) to attempt to parse and resolve the references. There are limitations to this. Consider the schema:
|
|
387
|
+
|
|
388
|
+
```json
|
|
389
|
+
{
|
|
390
|
+
"$schema": "https://json-schema.org/draft-04/schema",
|
|
391
|
+
"title": "Reusable Definitions",
|
|
392
|
+
"type": "object",
|
|
393
|
+
"id": "https://raw.githubusercontent.com/json-editor/json-editor/master/tests/fixtures/definitions.json",
|
|
394
|
+
"definitions": {
|
|
395
|
+
"address": {
|
|
396
|
+
"title": "Address",
|
|
397
|
+
"type": "object",
|
|
398
|
+
"properties": {
|
|
399
|
+
"street_address": { "type": "string" },
|
|
400
|
+
"city": { "type": "string" },
|
|
401
|
+
"state": { "type": "string" }
|
|
402
|
+
},
|
|
403
|
+
"required": ["street_address"]
|
|
404
|
+
},
|
|
405
|
+
"link" : {"$refs": "./properties.json#/properties/title"}
|
|
406
|
+
},
|
|
407
|
+
"properties": {
|
|
408
|
+
"address" : {"$refs": "#/definitions/address"}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
Where the definition "link" refers to a schema held in a directory that the resolver does not know about, we will not be able to fully resolve the schema which will likely cause errors in validation of the openAPI 3.0.X specification.
|
|
413
|
+
|
|
414
|
+
Because of the dependency we use to parse externally linked schemas, we can supply our own options to resolve schemas that are more difficult than a straight forward example.
|
|
415
|
+
|
|
416
|
+
You can create your own options file: https://apitools.dev/json-schema-ref-parser/docs/options.html to pass into the dependency that contains it's own resolver to allow you to resolve references that might be in hard to reach places. In your main project folder, you should have a folder called `options` with a file called `ref-parser.js` that looks like:
|
|
417
|
+
|
|
418
|
+
```js
|
|
419
|
+
'use strict'
|
|
420
|
+
|
|
421
|
+
// options from: https://apitools.dev/json-schema-ref-parser/docs/options.html
|
|
422
|
+
|
|
423
|
+
module.exports = {
|
|
424
|
+
continueOnError: true, // Don't throw on the first error
|
|
425
|
+
parse: {
|
|
426
|
+
json: false, // Disable the JSON parser
|
|
427
|
+
yaml: {
|
|
428
|
+
allowEmpty: false // Don't allow empty YAML files
|
|
429
|
+
},
|
|
430
|
+
text: {
|
|
431
|
+
canParse: [".txt", ".html"], // Parse .txt and .html files as plain text (strings)
|
|
432
|
+
encoding: 'utf16' // Use UTF-16 encoding
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
resolve: {
|
|
436
|
+
file: false, // Don't resolve local file references
|
|
437
|
+
http: {
|
|
438
|
+
timeout: 2000, // 2 second timeout
|
|
439
|
+
withCredentials: true, // Include auth credentials when resolving HTTP references
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
dereference: {
|
|
443
|
+
circular: false, // Don't allow circular $refs
|
|
444
|
+
excludedPathMatcher: (path) => // Skip dereferencing content under any 'example' key
|
|
445
|
+
path.includes("/example/")
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
If you don't supply this file, it will use the default options.
|
|
451
|
+
|
|
355
452
|
## License
|
|
356
453
|
|
|
357
454
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverless-openapi-documenter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -28,12 +28,16 @@
|
|
|
28
28
|
},
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@apidevtools/json-schema-ref-parser": "^9.0.9",
|
|
31
32
|
"chalk": "^4.1.2",
|
|
32
33
|
"js-yaml": "^4.1.0",
|
|
33
|
-
"json-schema-for-openapi": "^0.1.
|
|
34
|
+
"json-schema-for-openapi": "^0.1.2",
|
|
34
35
|
"oas-validator": "^5.0.8",
|
|
35
36
|
"openapi-to-postmanv2": "^3.2.0",
|
|
36
37
|
"swagger2openapi": "^7.0.8",
|
|
37
38
|
"uuid": "^8.3.2"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=14"
|
|
38
42
|
}
|
|
39
43
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
3
5
|
const { v4: uuid } = require('uuid')
|
|
4
6
|
const validator = require('oas-validator');
|
|
5
7
|
const SchemaConvertor = require('json-schema-for-openapi')
|
|
8
|
+
const $RefParser = require("@apidevtools/json-schema-ref-parser");
|
|
6
9
|
|
|
7
10
|
class DefinitionGenerator {
|
|
8
11
|
constructor(serverless, options = {}) {
|
|
@@ -24,16 +27,35 @@ class DefinitionGenerator {
|
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
this.operationIds = []
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
this.refParserOptions = require(path.resolve('options', 'ref-parser.js'))
|
|
33
|
+
} catch (err) {
|
|
34
|
+
this.refParserOptions = {}
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
parse() {
|
|
39
|
+
async parse() {
|
|
30
40
|
this.createInfo()
|
|
31
|
-
this.createPaths()
|
|
41
|
+
await this.createPaths()
|
|
42
|
+
.catch(err => {
|
|
43
|
+
throw err
|
|
44
|
+
})
|
|
45
|
+
|
|
32
46
|
if (this.serverless.service.custom.documentation.servers) {
|
|
33
47
|
const servers = this.createServers(this.serverless.service.custom.documentation.servers)
|
|
34
48
|
Object.assign(this.openAPI, {servers: servers})
|
|
35
49
|
}
|
|
36
|
-
|
|
50
|
+
|
|
51
|
+
if (this.serverless.service.custom.documentation.tags) {
|
|
52
|
+
this.createTags()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (this.serverless.service.custom.documentation.externalDocumentation) {
|
|
56
|
+
const extDoc = this.createExternalDocumentation(this.serverless.service.custom.documentation.externalDocumentation)
|
|
57
|
+
Object.assign(this.openAPI, {externalDocs: extDoc})
|
|
58
|
+
}
|
|
37
59
|
}
|
|
38
60
|
|
|
39
61
|
createInfo() {
|
|
@@ -48,7 +70,7 @@ class DefinitionGenerator {
|
|
|
48
70
|
Object.assign(this.openAPI, {info})
|
|
49
71
|
}
|
|
50
72
|
|
|
51
|
-
createPaths() {
|
|
73
|
+
async createPaths() {
|
|
52
74
|
const paths = {}
|
|
53
75
|
const httpFunctions = this.getHTTPFunctions()
|
|
54
76
|
|
|
@@ -65,7 +87,11 @@ class DefinitionGenerator {
|
|
|
65
87
|
opId = `${httpFunction.functionInfo.name}-${uuid()}`
|
|
66
88
|
}
|
|
67
89
|
|
|
68
|
-
const path = this.createOperationObject(event.http.method || event.httpApi.method, documentation, opId)
|
|
90
|
+
const path = await this.createOperationObject(event.http.method || event.httpApi.method, documentation, opId)
|
|
91
|
+
.catch(err => {
|
|
92
|
+
throw err
|
|
93
|
+
})
|
|
94
|
+
|
|
69
95
|
if (httpFunction.functionInfo?.summary)
|
|
70
96
|
path.summary = httpFunction.functionInfo.summary
|
|
71
97
|
|
|
@@ -121,14 +147,35 @@ class DefinitionGenerator {
|
|
|
121
147
|
return newServers
|
|
122
148
|
}
|
|
123
149
|
|
|
124
|
-
createExternalDocumentation() {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
150
|
+
createExternalDocumentation(docs) {
|
|
151
|
+
return {...docs}
|
|
152
|
+
// const documentation = this.serverless.service.custom.documentation
|
|
153
|
+
// if (documentation.externalDocumentation) {
|
|
154
|
+
// // Object.assign(this.openAPI, {externalDocs: {...documentation.externalDocumentation}})
|
|
155
|
+
// return
|
|
156
|
+
// }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
createTags() {
|
|
160
|
+
const tags = []
|
|
161
|
+
for (const tag of this.serverless.service.custom.documentation.tags) {
|
|
162
|
+
const obj = {
|
|
163
|
+
name: tag.name,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (tag.description) {
|
|
167
|
+
obj.description = tag.description
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (tag.externalDocumentation) {
|
|
171
|
+
obj.externalDocs = this.createExternalDocumentation(tag.externalDocumentation)
|
|
172
|
+
}
|
|
173
|
+
tags.push(obj)
|
|
128
174
|
}
|
|
175
|
+
Object.assign(this.openAPI, {tags: tags})
|
|
129
176
|
}
|
|
130
177
|
|
|
131
|
-
createOperationObject(method, documentation, name = uuid()) {
|
|
178
|
+
async createOperationObject(method, documentation, name = uuid()) {
|
|
132
179
|
const obj = {
|
|
133
180
|
summary: documentation.summary || '',
|
|
134
181
|
description: documentation.description || '',
|
|
@@ -138,22 +185,34 @@ class DefinitionGenerator {
|
|
|
138
185
|
}
|
|
139
186
|
|
|
140
187
|
if (documentation.pathParams) {
|
|
141
|
-
const paramObject = this.createParamObject('path', documentation)
|
|
188
|
+
const paramObject = await this.createParamObject('path', documentation)
|
|
189
|
+
.catch(err => {
|
|
190
|
+
throw err
|
|
191
|
+
})
|
|
142
192
|
obj.parameters = obj.parameters.concat(paramObject)
|
|
143
193
|
}
|
|
144
194
|
|
|
145
195
|
if (documentation.queryParams) {
|
|
146
|
-
const paramObject = this.createParamObject('query', documentation)
|
|
196
|
+
const paramObject = await this.createParamObject('query', documentation)
|
|
197
|
+
.catch(err => {
|
|
198
|
+
throw err
|
|
199
|
+
})
|
|
147
200
|
obj.parameters = obj.parameters.concat(paramObject)
|
|
148
201
|
}
|
|
149
202
|
|
|
150
203
|
if (documentation.headerParams) {
|
|
151
|
-
const paramObject = this.createParamObject('header', documentation)
|
|
204
|
+
const paramObject = await this.createParamObject('header', documentation)
|
|
205
|
+
.catch(err => {
|
|
206
|
+
throw err
|
|
207
|
+
})
|
|
152
208
|
obj.parameters = obj.parameters.concat(paramObject)
|
|
153
209
|
}
|
|
154
210
|
|
|
155
211
|
if (documentation.cookieParams) {
|
|
156
|
-
const paramObject = this.createParamObject('cookie', documentation)
|
|
212
|
+
const paramObject = await this.createParamObject('cookie', documentation)
|
|
213
|
+
.catch(err => {
|
|
214
|
+
throw err
|
|
215
|
+
})
|
|
157
216
|
obj.parameters = obj.parameters.concat(paramObject)
|
|
158
217
|
}
|
|
159
218
|
|
|
@@ -165,10 +224,16 @@ class DefinitionGenerator {
|
|
|
165
224
|
obj.deprecated = documentation.deprecated
|
|
166
225
|
|
|
167
226
|
if (documentation.requestBody)
|
|
168
|
-
obj.requestBody = this.createRequestBody(documentation)
|
|
227
|
+
obj.requestBody = await this.createRequestBody(documentation)
|
|
228
|
+
.catch(err => {
|
|
229
|
+
throw err
|
|
230
|
+
})
|
|
169
231
|
|
|
170
232
|
if (documentation.methodResponses)
|
|
171
|
-
obj.responses = this.createResponses(documentation)
|
|
233
|
+
obj.responses = await this.createResponses(documentation)
|
|
234
|
+
.catch(err => {
|
|
235
|
+
throw err
|
|
236
|
+
})
|
|
172
237
|
|
|
173
238
|
if (documentation.servers) {
|
|
174
239
|
const servers = this.createServers(documentation.servers)
|
|
@@ -178,14 +243,17 @@ class DefinitionGenerator {
|
|
|
178
243
|
return {[method]: obj}
|
|
179
244
|
}
|
|
180
245
|
|
|
181
|
-
createResponses(documentation) {
|
|
246
|
+
async createResponses(documentation) {
|
|
182
247
|
const responses = {}
|
|
183
248
|
for (const response of documentation.methodResponses) {
|
|
184
249
|
const obj = {
|
|
185
250
|
description: response.responseBody.description || '',
|
|
186
251
|
}
|
|
187
252
|
|
|
188
|
-
obj.content = this.createMediaTypeObject(response.responseModels, 'responses')
|
|
253
|
+
obj.content = await this.createMediaTypeObject(response.responseModels, 'responses')
|
|
254
|
+
.catch(err => {
|
|
255
|
+
throw err
|
|
256
|
+
})
|
|
189
257
|
|
|
190
258
|
Object.assign(responses,{[response.statusCode]: obj})
|
|
191
259
|
}
|
|
@@ -193,18 +261,21 @@ class DefinitionGenerator {
|
|
|
193
261
|
return responses
|
|
194
262
|
}
|
|
195
263
|
|
|
196
|
-
createRequestBody(documentation) {
|
|
264
|
+
async createRequestBody(documentation) {
|
|
197
265
|
const obj = {
|
|
198
266
|
description: documentation.requestBody.description,
|
|
199
267
|
required: documentation.requestBody.required || false,
|
|
200
268
|
}
|
|
201
269
|
|
|
202
|
-
obj.content = this.createMediaTypeObject(documentation.requestModels, 'requestBody')
|
|
270
|
+
obj.content = await this.createMediaTypeObject(documentation.requestModels, 'requestBody')
|
|
271
|
+
.catch(err => {
|
|
272
|
+
throw err
|
|
273
|
+
})
|
|
203
274
|
|
|
204
275
|
return obj
|
|
205
276
|
}
|
|
206
277
|
|
|
207
|
-
createMediaTypeObject(models, type) {
|
|
278
|
+
async createMediaTypeObject(models, type) {
|
|
208
279
|
const mediaTypeObj = {}
|
|
209
280
|
for (const mediaTypeDocumentation of this.serverless.service.custom.documentation.models) {
|
|
210
281
|
if (Object.values(models).includes(mediaTypeDocumentation.name)) {
|
|
@@ -222,59 +293,12 @@ class DefinitionGenerator {
|
|
|
222
293
|
obj.examples = this.createExamples(mediaTypeDocumentation.examples)
|
|
223
294
|
|
|
224
295
|
if (mediaTypeDocumentation.content[contentKey].schema) {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (this.openAPI?.components) {
|
|
233
|
-
if (this.openAPI.components?.schemas) {
|
|
234
|
-
const schemaObj = {
|
|
235
|
-
[mediaTypeDocumentation.name]: schema.schemas[key]
|
|
236
|
-
}
|
|
237
|
-
Object.assign(this.openAPI.components.schemas, schemaObj)
|
|
238
|
-
} else {
|
|
239
|
-
const schemaObj = {
|
|
240
|
-
[mediaTypeDocumentation.name]: schema.schemas[key]
|
|
241
|
-
}
|
|
242
|
-
Object.assign(this.openAPI.components, {schemas: schemaObj})
|
|
243
|
-
}
|
|
244
|
-
} else {
|
|
245
|
-
const components = {
|
|
246
|
-
components: {
|
|
247
|
-
schemas: {
|
|
248
|
-
[mediaTypeDocumentation.name]: schema.schemas[key]
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
Object.assign(this.openAPI, components)
|
|
253
|
-
}
|
|
254
|
-
} else {
|
|
255
|
-
if (this.openAPI?.components) {
|
|
256
|
-
if (this.openAPI.components?.schemas) {
|
|
257
|
-
const schemaObj = {
|
|
258
|
-
[key]: schema.schemas[key]
|
|
259
|
-
}
|
|
260
|
-
Object.assign(this.openAPI.components.schemas, schemaObj)
|
|
261
|
-
} else {
|
|
262
|
-
const schemaObj = {
|
|
263
|
-
[key]: schema.schemas[key]
|
|
264
|
-
}
|
|
265
|
-
Object.assign(this.openAPI.components, {schemas: schemaObj})
|
|
266
|
-
}
|
|
267
|
-
} else {
|
|
268
|
-
const components = {
|
|
269
|
-
components: {
|
|
270
|
-
schemas: {
|
|
271
|
-
[key]: schema.schemas[key]
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
Object.assign(this.openAPI, components)
|
|
276
|
-
}
|
|
277
|
-
}
|
|
296
|
+
const schemaRef = await this.schemaCreator(mediaTypeDocumentation.content[contentKey].schema, mediaTypeDocumentation.name)
|
|
297
|
+
.catch(err => {
|
|
298
|
+
throw err
|
|
299
|
+
})
|
|
300
|
+
obj.schema = {
|
|
301
|
+
$ref: schemaRef
|
|
278
302
|
}
|
|
279
303
|
}
|
|
280
304
|
|
|
@@ -284,7 +308,7 @@ class DefinitionGenerator {
|
|
|
284
308
|
return mediaTypeObj
|
|
285
309
|
}
|
|
286
310
|
|
|
287
|
-
createParamObject(paramIn, documentation) {
|
|
311
|
+
async createParamObject(paramIn, documentation) {
|
|
288
312
|
const params = []
|
|
289
313
|
for (const param of documentation[`${paramIn}Params`]) {
|
|
290
314
|
const obj = {
|
|
@@ -305,7 +329,7 @@ class DefinitionGenerator {
|
|
|
305
329
|
if (param.style)
|
|
306
330
|
obj.style = param.style
|
|
307
331
|
|
|
308
|
-
if (param.explode)
|
|
332
|
+
if (Object.keys(param).includes('explode'))
|
|
309
333
|
obj.explode = param.explode
|
|
310
334
|
|
|
311
335
|
if (paramIn === 'query' && param.allowReserved)
|
|
@@ -318,10 +342,12 @@ class DefinitionGenerator {
|
|
|
318
342
|
obj.examples = this.createExamples(param.examples)
|
|
319
343
|
|
|
320
344
|
if (param.schema) {
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
345
|
+
const schemaRef = await this.schemaCreator(param.schema, param.name)
|
|
346
|
+
.catch(err => {
|
|
347
|
+
throw err
|
|
348
|
+
})
|
|
349
|
+
obj.schema = {
|
|
350
|
+
$ref: schemaRef
|
|
325
351
|
}
|
|
326
352
|
}
|
|
327
353
|
|
|
@@ -330,6 +356,55 @@ class DefinitionGenerator {
|
|
|
330
356
|
return params;
|
|
331
357
|
}
|
|
332
358
|
|
|
359
|
+
async schemaCreator(schema, name) {
|
|
360
|
+
const addToComponents = (schema, name) => {
|
|
361
|
+
const schemaObj = {
|
|
362
|
+
[name]: schema
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (this.openAPI?.components) {
|
|
366
|
+
if (this.openAPI.components?.schemas) {
|
|
367
|
+
Object.assign(this.openAPI.components.schemas, schemaObj)
|
|
368
|
+
} else {
|
|
369
|
+
Object.assign(this.openAPI.components, {schemas: schemaObj})
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
const components = {
|
|
373
|
+
components: {
|
|
374
|
+
schemas: schemaObj
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
Object.assign(this.openAPI, components)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (typeof schema !== 'string' && Object.keys(schema).length > 0) {
|
|
383
|
+
const convertedSchema = SchemaConvertor.convert(schema)
|
|
384
|
+
for (const key of Object.keys(convertedSchema.schemas)) {
|
|
385
|
+
if (key === 'main' || key.split('-')[0] === 'main') {
|
|
386
|
+
const ref = `#/components/schemas/${name}`
|
|
387
|
+
|
|
388
|
+
addToComponents(convertedSchema.schemas[key], name)
|
|
389
|
+
return ref
|
|
390
|
+
} else {
|
|
391
|
+
addToComponents(convertedSchema.schemas[key], key)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
const combinedSchema = await $RefParser.dereference(schema, this.refParserOptions)
|
|
396
|
+
.catch(err => {
|
|
397
|
+
console.error(err)
|
|
398
|
+
throw err
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
return await this.schemaCreator(combinedSchema, name)
|
|
402
|
+
.catch(err => {
|
|
403
|
+
throw err
|
|
404
|
+
})
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
333
408
|
createExamples(examples) {
|
|
334
409
|
const examplesObj = {}
|
|
335
410
|
|
package/src/openAPIGenerator.js
CHANGED
|
@@ -67,11 +67,18 @@ class OpenAPIGenerator {
|
|
|
67
67
|
required: ['documentation'],
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
this.serverless.configSchemaHandler.defineFunctionEventProperties('aws', 'httpApi', {
|
|
71
|
+
properties: {
|
|
72
|
+
documentation: { type: 'object' },
|
|
73
|
+
},
|
|
74
|
+
required: ['documentation'],
|
|
75
|
+
});
|
|
76
|
+
|
|
70
77
|
this.serverless.configSchemaHandler.defineFunctionProperties('aws', {
|
|
71
78
|
properties: {
|
|
72
79
|
// description: {type: 'string'},
|
|
73
80
|
summary: {type: 'string'},
|
|
74
|
-
servers: {
|
|
81
|
+
servers: {anyOf: [{type:'object'}, {type:'array'}]},
|
|
75
82
|
}
|
|
76
83
|
})
|
|
77
84
|
}
|
|
@@ -97,12 +104,15 @@ class OpenAPIGenerator {
|
|
|
97
104
|
const config = this.processCliInput()
|
|
98
105
|
const generator = new DefinitionGenerator(this.serverless);
|
|
99
106
|
|
|
100
|
-
generator.parse()
|
|
107
|
+
await generator.parse()
|
|
108
|
+
.catch(err => {
|
|
109
|
+
this.log('error', chalk.bold.red(`ERROR: An error was thrown generating the OpenAPI v3 documentation`))
|
|
110
|
+
throw new this.serverless.classes.Error(err)
|
|
111
|
+
})
|
|
101
112
|
|
|
102
113
|
const valid = await generator.validate()
|
|
103
114
|
.catch(err => {
|
|
104
|
-
|
|
105
|
-
this.log('error', chalk.bold.red(`ERROR: An error was thrown generation the OpenAPI v3 documentation`))
|
|
115
|
+
this.log('error', chalk.bold.red(`ERROR: An error was thrown validating the OpenAPI v3 documentation`))
|
|
106
116
|
throw new this.serverless.classes.Error(err)
|
|
107
117
|
})
|
|
108
118
|
|
|
@@ -19,6 +19,8 @@ functions:
|
|
|
19
19
|
documentation:
|
|
20
20
|
summary: Create User
|
|
21
21
|
description: Creates a user and then sends a generated password email
|
|
22
|
+
tags:
|
|
23
|
+
- jesus
|
|
22
24
|
externalDocumentation:
|
|
23
25
|
url: https://bing.com
|
|
24
26
|
description: A link to bing
|
|
@@ -67,6 +69,12 @@ custom:
|
|
|
67
69
|
documentation:
|
|
68
70
|
description: This is a description of what this does
|
|
69
71
|
version: 1.0.0
|
|
72
|
+
tags:
|
|
73
|
+
- name: jesus
|
|
74
|
+
description: jesus was a man
|
|
75
|
+
externalDocumentation:
|
|
76
|
+
url: https://whitehouse.gov
|
|
77
|
+
description: a link to the whitehouse
|
|
70
78
|
externalDocumentation:
|
|
71
79
|
url: https://google.com
|
|
72
80
|
description: A link to google
|