serverless-openapi-documenter 0.0.71 → 0.0.80
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 +56 -1
- package/package.json +1 -1
- package/src/definitionGenerator.js +879 -730
- package/test/unit/openAPIGenerator.spec.js +241 -200
|
@@ -1,827 +1,976 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const path = require(
|
|
3
|
+
const path = require("path");
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const validator = require(
|
|
5
|
+
const isEqual = require("lodash.isequal");
|
|
6
|
+
const validator = require("oas-validator");
|
|
7
|
+
const { v4: uuid } = require("uuid");
|
|
7
8
|
|
|
8
|
-
const SchemaHandler = require(
|
|
9
|
-
const oWASP = require(
|
|
9
|
+
const SchemaHandler = require("./schemaHandler");
|
|
10
|
+
const oWASP = require("./owasp");
|
|
10
11
|
|
|
11
12
|
class DefinitionGenerator {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
13
|
+
constructor(serverless, options = {}) {
|
|
14
|
+
this.version =
|
|
15
|
+
serverless?.processedInput?.options?.openApiVersion || "3.0.0";
|
|
16
|
+
|
|
17
|
+
this.serverless = serverless;
|
|
18
|
+
this.httpKeys = {
|
|
19
|
+
http: "http",
|
|
20
|
+
httpAPI: "httpApi",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
this.componentsSchemas = {
|
|
24
|
+
requestBody: "requestBodies",
|
|
25
|
+
responses: "responses",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
this.openAPI = {
|
|
29
|
+
openapi: this.version,
|
|
30
|
+
components: {
|
|
31
|
+
schemas: {},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
this.schemaHandler = new SchemaHandler(serverless, this.openAPI);
|
|
36
|
+
|
|
37
|
+
this.operationIdMap = {};
|
|
38
|
+
this.functionMap = {};
|
|
39
|
+
|
|
40
|
+
this.operationIds = [];
|
|
41
|
+
this.schemaIDs = [];
|
|
42
|
+
|
|
43
|
+
this.componentTypes = {
|
|
44
|
+
schemas: "schemas",
|
|
45
|
+
securitySchemes: "securitySchemes",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
this.DEFAULT_CORS_HEADERS = {
|
|
49
|
+
"Access-Control-Allow-Origin": {
|
|
50
|
+
description:
|
|
51
|
+
"The Access-Control-Allow-Origin response header indicates whether the response can be shared with requesting code from the given [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin). - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)",
|
|
52
|
+
schema: {
|
|
53
|
+
type: "string",
|
|
54
|
+
default: "*",
|
|
55
|
+
example: "https://developer.mozilla.org",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
"Access-Control-Allow-Credentials": {
|
|
59
|
+
description: `The Access-Control-Allow-Credentials response header tells browsers whether to expose the response to the frontend JavaScript code when the request's credentials mode ([Request.credentials](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)) is include. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials)`,
|
|
60
|
+
schema: {
|
|
61
|
+
type: "boolean",
|
|
62
|
+
default: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
this.refParserOptions = require(path.resolve("options", "ref-parser.js"));
|
|
69
|
+
} catch (err) {
|
|
70
|
+
this.refParserOptions = {};
|
|
67
71
|
}
|
|
72
|
+
}
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
await oWASP.getLatest()
|
|
74
|
+
async parse() {
|
|
75
|
+
this.createInfo();
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
.catch(err => {
|
|
76
|
-
throw err
|
|
77
|
-
})
|
|
77
|
+
await oWASP.getLatest();
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
await this.schemaHandler.addModelsToOpenAPI().catch((err) => {
|
|
80
|
+
throw err;
|
|
81
|
+
});
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
if (this.serverless.service.custom.documentation.securitySchemes) {
|
|
84
|
+
this.createSecuritySchemes(
|
|
85
|
+
this.serverless.service.custom.documentation.securitySchemes
|
|
86
|
+
);
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (this.serverless.service.custom.documentation.servers) {
|
|
93
|
-
const servers = this.createServers(this.serverless.service.custom.documentation.servers)
|
|
94
|
-
Object.assign(this.openAPI, { servers: servers })
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (this.serverless.service.custom.documentation.tags) {
|
|
98
|
-
this.createTags()
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (this.serverless.service.custom.documentation.externalDocumentation) {
|
|
102
|
-
const extDoc = this.createExternalDocumentation(this.serverless.service.custom.documentation.externalDocumentation)
|
|
103
|
-
Object.assign(this.openAPI, { externalDocs: extDoc })
|
|
104
|
-
}
|
|
88
|
+
if (this.serverless.service.custom.documentation.security) {
|
|
89
|
+
this.openAPI.security =
|
|
90
|
+
this.serverless.service.custom.documentation.security;
|
|
91
|
+
}
|
|
105
92
|
}
|
|
106
93
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const info = {
|
|
112
|
-
title: documentation?.title || service.service,
|
|
113
|
-
description: documentation?.description || '',
|
|
114
|
-
version: documentation?.version || uuid(),
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (documentation.termsOfService)
|
|
118
|
-
info.termsOfService = documentation.termsOfService
|
|
119
|
-
|
|
120
|
-
if (documentation.contact) {
|
|
121
|
-
const contactObj = {}
|
|
122
|
-
contactObj.name = documentation.contact.name || ''
|
|
94
|
+
await this.createPaths().catch((err) => {
|
|
95
|
+
throw err;
|
|
96
|
+
});
|
|
123
97
|
|
|
124
|
-
|
|
125
|
-
contactObj.url = documentation.contact.url
|
|
98
|
+
this.cleanupLinks();
|
|
126
99
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
licenseObj.name = documentation.license.name || ''
|
|
134
|
-
|
|
135
|
-
if (documentation.license.url)
|
|
136
|
-
licenseObj.url = documentation.license.url || ''
|
|
137
|
-
|
|
138
|
-
Object.assign(info, { license: licenseObj })
|
|
139
|
-
}
|
|
100
|
+
if (this.serverless.service.custom.documentation.servers) {
|
|
101
|
+
const servers = this.createServers(
|
|
102
|
+
this.serverless.service.custom.documentation.servers
|
|
103
|
+
);
|
|
104
|
+
Object.assign(this.openAPI, { servers: servers });
|
|
105
|
+
}
|
|
140
106
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
}
|
|
107
|
+
if (this.serverless.service.custom.documentation.tags) {
|
|
108
|
+
this.createTags();
|
|
109
|
+
}
|
|
146
110
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const httpFunctions = this.getHTTPFunctions()
|
|
153
|
-
for (const httpFunction of httpFunctions) {
|
|
154
|
-
for (const event of httpFunction.event) {
|
|
155
|
-
if (event?.http?.documentation || event?.httpApi?.documentation) {
|
|
156
|
-
this.currentEvent = event?.http || event?.httpApi
|
|
157
|
-
const documentation = event?.http?.documentation || event?.httpApi?.documentation
|
|
158
|
-
|
|
159
|
-
this.currentFunctionName = httpFunction.functionInfo.name
|
|
160
|
-
|
|
161
|
-
let opId
|
|
162
|
-
if (this.operationIds.includes(httpFunction.functionInfo.name) === false) {
|
|
163
|
-
opId = httpFunction.functionInfo.name
|
|
164
|
-
this.operationIds.push(opId)
|
|
165
|
-
} else {
|
|
166
|
-
opId = `${httpFunction.functionInfo.name}-${uuid()}`
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const path = await this.createOperationObject(event?.http?.method || event?.httpApi?.method, documentation, opId)
|
|
170
|
-
.catch(err => {
|
|
171
|
-
throw err
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
if (httpFunction.functionInfo?.summary)
|
|
175
|
-
path.summary = httpFunction.functionInfo.summary
|
|
176
|
-
|
|
177
|
-
if (httpFunction.functionInfo?.description)
|
|
178
|
-
path.description = httpFunction.functionInfo.description
|
|
179
|
-
|
|
180
|
-
if (httpFunction.functionInfo?.servers) {
|
|
181
|
-
const servers = this.createServers(httpFunction.functionInfo.servers)
|
|
182
|
-
path.servers = servers
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let slashPath = (event?.http?.path || event.httpApi?.path) ?? '/'
|
|
186
|
-
const pathStart = new RegExp(/^\//, 'g')
|
|
187
|
-
if (pathStart.test(slashPath) === false) {
|
|
188
|
-
slashPath = `/${(event?.http?.path || event.httpApi?.path) ?? ''}`
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (paths[slashPath]) {
|
|
192
|
-
Object.assign(paths[slashPath], path);
|
|
193
|
-
} else {
|
|
194
|
-
Object.assign(paths, { [slashPath]: path });
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
Object.assign(this.openAPI, { paths })
|
|
111
|
+
if (this.serverless.service.custom.documentation.externalDocumentation) {
|
|
112
|
+
const extDoc = this.createExternalDocumentation(
|
|
113
|
+
this.serverless.service.custom.documentation.externalDocumentation
|
|
114
|
+
);
|
|
115
|
+
Object.assign(this.openAPI, { externalDocs: extDoc });
|
|
200
116
|
}
|
|
117
|
+
}
|
|
201
118
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
119
|
+
createInfo() {
|
|
120
|
+
const service = this.serverless.service;
|
|
121
|
+
const documentation = this.serverless.service.custom.documentation;
|
|
205
122
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
123
|
+
const info = {
|
|
124
|
+
title: documentation?.title || service.service,
|
|
125
|
+
description: documentation?.description || "",
|
|
126
|
+
version: documentation?.version || uuid(),
|
|
127
|
+
};
|
|
211
128
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
129
|
+
if (documentation.termsOfService)
|
|
130
|
+
info.termsOfService = documentation.termsOfService;
|
|
215
131
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
132
|
+
if (documentation.contact) {
|
|
133
|
+
const contactObj = {};
|
|
134
|
+
contactObj.name = documentation.contact.name || "";
|
|
219
135
|
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
} else {
|
|
223
|
-
const obj = {
|
|
224
|
-
url: servers.url,
|
|
225
|
-
}
|
|
136
|
+
if (documentation.contact.url) contactObj.url = documentation.contact.url;
|
|
226
137
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
138
|
+
contactObj.email = documentation.contact.email || "";
|
|
139
|
+
Object.assign(info, { contact: contactObj });
|
|
140
|
+
}
|
|
230
141
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
142
|
+
if (documentation.license && documentation.license.name) {
|
|
143
|
+
const licenseObj = {};
|
|
144
|
+
licenseObj.name = documentation.license.name || "";
|
|
234
145
|
|
|
235
|
-
|
|
236
|
-
|
|
146
|
+
if (documentation.license.url)
|
|
147
|
+
licenseObj.url = documentation.license.url || "";
|
|
237
148
|
|
|
238
|
-
|
|
149
|
+
Object.assign(info, { license: licenseObj });
|
|
239
150
|
}
|
|
240
151
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// // Object.assign(this.openAPI, {externalDocs: {...documentation.externalDocumentation}})
|
|
246
|
-
// return
|
|
247
|
-
// }
|
|
152
|
+
for (const key of Object.keys(documentation)) {
|
|
153
|
+
if (/^[x\-]/i.test(key)) {
|
|
154
|
+
Object.assign(info, { [key]: documentation[key] });
|
|
155
|
+
}
|
|
248
156
|
}
|
|
249
157
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
158
|
+
Object.assign(this.openAPI, { info });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async createPaths() {
|
|
162
|
+
const paths = {};
|
|
163
|
+
const httpFunctions = this.getHTTPFunctions();
|
|
164
|
+
|
|
165
|
+
for (const httpFunction of httpFunctions) {
|
|
166
|
+
for (const event of httpFunction.event) {
|
|
167
|
+
if (event?.http?.documentation || event?.httpApi?.documentation) {
|
|
168
|
+
this.currentEvent = event?.http || event?.httpApi;
|
|
169
|
+
const documentation =
|
|
170
|
+
event?.http?.documentation || event?.httpApi?.documentation;
|
|
171
|
+
|
|
172
|
+
this.currentFunctionName = httpFunction.functionInfo.name;
|
|
173
|
+
this.operationName = httpFunction.operationName;
|
|
174
|
+
|
|
175
|
+
const path = await this.createOperationObject(
|
|
176
|
+
event?.http?.method || event?.httpApi?.method,
|
|
177
|
+
documentation
|
|
178
|
+
).catch((err) => {
|
|
179
|
+
throw err;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (httpFunction.functionInfo?.summary)
|
|
183
|
+
path.summary = httpFunction.functionInfo.summary;
|
|
184
|
+
|
|
185
|
+
if (httpFunction.functionInfo?.description)
|
|
186
|
+
path.description = httpFunction.functionInfo.description;
|
|
187
|
+
|
|
188
|
+
if (httpFunction.functionInfo?.servers) {
|
|
189
|
+
const servers = this.createServers(
|
|
190
|
+
httpFunction.functionInfo.servers
|
|
191
|
+
);
|
|
192
|
+
path.servers = servers;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let slashPath = (event?.http?.path || event.httpApi?.path) ?? "/";
|
|
196
|
+
const pathStart = new RegExp(/^\//, "g");
|
|
197
|
+
if (pathStart.test(slashPath) === false) {
|
|
198
|
+
slashPath = `/${(event?.http?.path || event.httpApi?.path) ?? ""}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (paths[slashPath]) {
|
|
202
|
+
Object.assign(paths[slashPath], path);
|
|
203
|
+
} else {
|
|
204
|
+
Object.assign(paths, { [slashPath]: path });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
267
208
|
}
|
|
209
|
+
Object.assign(this.openAPI, { paths });
|
|
210
|
+
}
|
|
268
211
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
description: documentation.description || '',
|
|
273
|
-
operationId: documentation.operationId || name,
|
|
274
|
-
parameters: [],
|
|
275
|
-
tags: documentation.tags || []
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (documentation.pathParams) {
|
|
279
|
-
const paramObject = await this.createParamObject('path', documentation)
|
|
280
|
-
.catch(err => {
|
|
281
|
-
throw err
|
|
282
|
-
})
|
|
283
|
-
obj.parameters = obj.parameters.concat(paramObject)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (documentation.queryParams) {
|
|
287
|
-
const paramObject = await this.createParamObject('query', documentation)
|
|
288
|
-
.catch(err => {
|
|
289
|
-
throw err
|
|
290
|
-
})
|
|
291
|
-
obj.parameters = obj.parameters.concat(paramObject)
|
|
292
|
-
}
|
|
212
|
+
createServers(servers) {
|
|
213
|
+
const serverDoc = servers;
|
|
214
|
+
const newServers = [];
|
|
293
215
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
obj.parameters = obj.parameters.concat(paramObject)
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (documentation.cookieParams) {
|
|
303
|
-
const paramObject = await this.createParamObject('cookie', documentation)
|
|
304
|
-
.catch(err => {
|
|
305
|
-
throw err
|
|
306
|
-
})
|
|
307
|
-
obj.parameters = obj.parameters.concat(paramObject)
|
|
308
|
-
}
|
|
216
|
+
if (Array.isArray(serverDoc)) {
|
|
217
|
+
for (const server of serverDoc) {
|
|
218
|
+
const obj = {
|
|
219
|
+
url: server.url,
|
|
220
|
+
};
|
|
309
221
|
|
|
310
|
-
if (
|
|
311
|
-
|
|
222
|
+
if (server.description) {
|
|
223
|
+
obj.description = server.description;
|
|
312
224
|
}
|
|
313
225
|
|
|
314
|
-
if (
|
|
315
|
-
|
|
226
|
+
if (server.variables) {
|
|
227
|
+
obj.variables = server.variables;
|
|
316
228
|
}
|
|
317
229
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
apiKeyName = schemeName
|
|
325
|
-
hasXAPIKey = true
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
230
|
+
newServers.push(obj);
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
const obj = {
|
|
234
|
+
url: servers.url,
|
|
235
|
+
};
|
|
329
236
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
237
|
+
if (servers.description) {
|
|
238
|
+
obj.description = servers.description;
|
|
239
|
+
}
|
|
333
240
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
obj.security = [{[apiKeyName]: []}]
|
|
338
|
-
}
|
|
339
|
-
}
|
|
241
|
+
if (servers.variables) {
|
|
242
|
+
obj.variables = servers.variables;
|
|
243
|
+
}
|
|
340
244
|
|
|
341
|
-
|
|
342
|
-
obj.deprecated = documentation.deprecated
|
|
343
|
-
|
|
344
|
-
if (documentation.requestBody || this.currentEvent?.request?.schemas) {
|
|
345
|
-
const requestModel = {}
|
|
346
|
-
if (documentation.requestBody) {
|
|
347
|
-
Object.assign(
|
|
348
|
-
requestModel,
|
|
349
|
-
{
|
|
350
|
-
description: documentation.requestBody.description,
|
|
351
|
-
models: documentation.requestModels,
|
|
352
|
-
required: documentation.requestBody.required
|
|
353
|
-
}
|
|
354
|
-
)
|
|
355
|
-
} else {
|
|
356
|
-
Object.assign(
|
|
357
|
-
requestModel,
|
|
358
|
-
{
|
|
359
|
-
description: '',
|
|
360
|
-
models: this.currentEvent?.request?.schemas
|
|
361
|
-
}
|
|
362
|
-
)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
obj.requestBody = await this.createRequestBody(requestModel)
|
|
366
|
-
.catch(err => {
|
|
367
|
-
throw err
|
|
368
|
-
})
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (documentation.methodResponses)
|
|
372
|
-
obj.responses = await this.createResponses(documentation)
|
|
373
|
-
.catch(err => {
|
|
374
|
-
throw err
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
if (documentation.servers) {
|
|
378
|
-
const servers = this.createServers(documentation.servers)
|
|
379
|
-
obj.servers = servers
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
return { [method.toLowerCase()]: obj }
|
|
245
|
+
newServers.push(obj);
|
|
383
246
|
}
|
|
384
247
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
owaspHeaders = await this.createResponseHeaders(oWASP.getHeaders(response.owasp))
|
|
415
|
-
.catch(err => {
|
|
416
|
-
throw err
|
|
417
|
-
})
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const corsHeaders = await this.corsHeaders()
|
|
422
|
-
.catch(err => {
|
|
423
|
-
throw err;
|
|
424
|
-
})
|
|
425
|
-
|
|
426
|
-
const addHeaders = (headers) => {
|
|
427
|
-
for (const key in headers) {
|
|
428
|
-
if (!(key in obj.headers) && (obj.headers[key] = {})) {
|
|
429
|
-
obj.headers[key] = headers[key]
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (obj.headers) {
|
|
435
|
-
addHeaders(corsHeaders)
|
|
436
|
-
addHeaders(owaspHeaders)
|
|
437
|
-
} else {
|
|
438
|
-
if (Object.keys(corsHeaders).length) {
|
|
439
|
-
obj.headers = corsHeaders
|
|
440
|
-
addHeaders(owaspHeaders)
|
|
441
|
-
} else {
|
|
442
|
-
obj.headers = owaspHeaders
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
Object.assign(responses, { [response.statusCode]: obj })
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return responses
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
async corsHeaders() {
|
|
453
|
-
let headers = {}
|
|
454
|
-
if (this.currentEvent?.cors === true) {
|
|
455
|
-
headers = await this.createResponseHeaders(this.DEFAULT_CORS_HEADERS)
|
|
456
|
-
.catch(err => {
|
|
457
|
-
throw err;
|
|
458
|
-
})
|
|
459
|
-
} else if (this.currentEvent.cors) {
|
|
460
|
-
const newHeaders = {}
|
|
461
|
-
for (const key of Object.keys(this.DEFAULT_CORS_HEADERS)) {
|
|
462
|
-
if (key === 'Access-Control-Allow-Credentials' &&
|
|
463
|
-
(this.currentEvent.cors.allowCredentials === undefined || this.currentEvent.cors?.allowCredentials === false)) {
|
|
464
|
-
continue
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const obj = JSON.parse(JSON.stringify(this.DEFAULT_CORS_HEADERS[key]))
|
|
468
|
-
|
|
469
|
-
if (key === 'Access-Control-Allow-Origin') {
|
|
470
|
-
if (this.currentEvent.cors?.origins || this.currentEvent.cors?.origin) {
|
|
471
|
-
obj.schema.example = this.currentEvent.cors?.origins?.toString() || this.currentEvent.cors?.origin?.toString()
|
|
472
|
-
} else if (this.currentEvent.cors?.allowedOrigins) {
|
|
473
|
-
obj.schema.example = this.currentEvent.cors.allowedOrigins.toString()
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
Object.assign(newHeaders, { [key]: obj })
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
headers = await this.createResponseHeaders(newHeaders)
|
|
481
|
-
.catch(err => {
|
|
482
|
-
throw err;
|
|
483
|
-
})
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
return headers;
|
|
248
|
+
return newServers;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
createExternalDocumentation(docs) {
|
|
252
|
+
return { ...docs };
|
|
253
|
+
// const documentation = this.serverless.service.custom.documentation
|
|
254
|
+
// if (documentation.externalDocumentation) {
|
|
255
|
+
// // Object.assign(this.openAPI, {externalDocs: {...documentation.externalDocumentation}})
|
|
256
|
+
// return
|
|
257
|
+
// }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
createTags() {
|
|
261
|
+
const tags = [];
|
|
262
|
+
for (const tag of this.serverless.service.custom.documentation.tags) {
|
|
263
|
+
const obj = {
|
|
264
|
+
name: tag.name,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
if (tag.description) {
|
|
268
|
+
obj.description = tag.description;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (tag.externalDocumentation) {
|
|
272
|
+
obj.externalDocs = this.createExternalDocumentation(
|
|
273
|
+
tag.externalDocumentation
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
tags.push(obj);
|
|
487
277
|
}
|
|
278
|
+
Object.assign(this.openAPI, { tags: tags });
|
|
279
|
+
}
|
|
488
280
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const newHeader = {}
|
|
494
|
-
newHeader.description = headers[header].description || ''
|
|
495
|
-
|
|
496
|
-
if (headers[header].schema) {
|
|
497
|
-
const schemaRef = await this.schemaHandler.createSchema(header, headers[header].schema)
|
|
498
|
-
.catch(err => {
|
|
499
|
-
throw err
|
|
500
|
-
})
|
|
501
|
-
newHeader.schema = {
|
|
502
|
-
$ref: schemaRef
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
Object.assign(obj, { [header]: newHeader })
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
return obj
|
|
281
|
+
async createOperationObject(method, documentation) {
|
|
282
|
+
let operationId = documentation?.operationId || this.operationName;
|
|
283
|
+
if (this.operationIds.includes(operationId)) {
|
|
284
|
+
operationId += `-${uuid()}`;
|
|
510
285
|
}
|
|
511
286
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
287
|
+
const arr = this.functionMap[this.operationName];
|
|
288
|
+
arr.push(operationId);
|
|
289
|
+
this.functionMap[this.operationName] = arr;
|
|
290
|
+
|
|
291
|
+
this.operationIds.push(operationId);
|
|
292
|
+
Object.assign(this.operationIdMap, {
|
|
293
|
+
[operationId]: this.operationName,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const obj = {
|
|
297
|
+
summary: documentation.summary || "",
|
|
298
|
+
description: documentation.description || "",
|
|
299
|
+
operationId: operationId,
|
|
300
|
+
parameters: [],
|
|
301
|
+
tags: documentation.tags || [],
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
if (documentation.pathParams) {
|
|
305
|
+
const paramObject = await this.createParamObject(
|
|
306
|
+
"path",
|
|
307
|
+
documentation
|
|
308
|
+
).catch((err) => {
|
|
309
|
+
throw err;
|
|
310
|
+
});
|
|
311
|
+
obj.parameters = obj.parameters.concat(paramObject);
|
|
524
312
|
}
|
|
525
313
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
314
|
+
if (documentation.queryParams) {
|
|
315
|
+
const paramObject = await this.createParamObject(
|
|
316
|
+
"query",
|
|
317
|
+
documentation
|
|
318
|
+
).catch((err) => {
|
|
319
|
+
throw err;
|
|
320
|
+
});
|
|
321
|
+
obj.parameters = obj.parameters.concat(paramObject);
|
|
322
|
+
}
|
|
532
323
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
324
|
+
if (documentation.headerParams) {
|
|
325
|
+
const paramObject = await this.createParamObject(
|
|
326
|
+
"header",
|
|
327
|
+
documentation
|
|
328
|
+
).catch((err) => {
|
|
329
|
+
throw err;
|
|
330
|
+
});
|
|
331
|
+
obj.parameters = obj.parameters.concat(paramObject);
|
|
332
|
+
}
|
|
540
333
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
334
|
+
if (documentation.cookieParams) {
|
|
335
|
+
const paramObject = await this.createParamObject(
|
|
336
|
+
"cookie",
|
|
337
|
+
documentation
|
|
338
|
+
).catch((err) => {
|
|
339
|
+
throw err;
|
|
340
|
+
});
|
|
341
|
+
obj.parameters = obj.parameters.concat(paramObject);
|
|
342
|
+
}
|
|
545
343
|
|
|
546
|
-
|
|
547
|
-
|
|
344
|
+
if (documentation.externalDocumentation) {
|
|
345
|
+
obj.externalDocs = documentation.externalDocumentation;
|
|
346
|
+
}
|
|
548
347
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
obj.example = mediaTypeDocumentation.example
|
|
348
|
+
if (Object.keys(documentation).includes("security")) {
|
|
349
|
+
obj.security = documentation.security;
|
|
350
|
+
}
|
|
553
351
|
|
|
554
|
-
|
|
555
|
-
|
|
352
|
+
if (this.currentEvent?.private && this.currentEvent.private === true) {
|
|
353
|
+
let apiKeyName = "x-api-key";
|
|
354
|
+
let hasXAPIKey = false;
|
|
355
|
+
if (this.openAPI?.components?.[this.componentTypes.securitySchemes]) {
|
|
356
|
+
for (const [schemeName, schemeValue] of Object.entries(
|
|
357
|
+
this.openAPI.components[this.componentTypes.securitySchemes]
|
|
358
|
+
)) {
|
|
359
|
+
if (
|
|
360
|
+
schemeValue.type === "apiKey" &&
|
|
361
|
+
schemeValue.name === "x-api-key"
|
|
362
|
+
) {
|
|
363
|
+
apiKeyName = schemeName;
|
|
364
|
+
hasXAPIKey = true;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (hasXAPIKey === false) {
|
|
370
|
+
this.createSecuritySchemes({
|
|
371
|
+
[apiKeyName]: { type: "apiKey", name: apiKeyName, in: "header" },
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (obj.security) {
|
|
376
|
+
obj.security.push({ [apiKeyName]: [] });
|
|
377
|
+
} else {
|
|
378
|
+
obj.security = [{ [apiKeyName]: [] }];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
556
381
|
|
|
557
|
-
|
|
558
|
-
|
|
382
|
+
if (Object.keys(documentation).includes("deprecated"))
|
|
383
|
+
obj.deprecated = documentation.deprecated;
|
|
384
|
+
|
|
385
|
+
if (documentation.requestBody || this.currentEvent?.request?.schemas) {
|
|
386
|
+
const requestModel = {};
|
|
387
|
+
if (documentation.requestBody) {
|
|
388
|
+
Object.assign(requestModel, {
|
|
389
|
+
description: documentation.requestBody.description,
|
|
390
|
+
models: documentation.requestModels,
|
|
391
|
+
required: documentation.requestBody.required,
|
|
392
|
+
});
|
|
393
|
+
} else {
|
|
394
|
+
Object.assign(requestModel, {
|
|
395
|
+
description: "",
|
|
396
|
+
models: this.currentEvent?.request?.schemas,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
obj.requestBody = await this.createRequestBody(requestModel).catch(
|
|
401
|
+
(err) => {
|
|
402
|
+
throw err;
|
|
403
|
+
}
|
|
404
|
+
);
|
|
405
|
+
}
|
|
559
406
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
407
|
+
if (documentation.methodResponses)
|
|
408
|
+
obj.responses = await this.createResponses(documentation).catch((err) => {
|
|
409
|
+
throw err;
|
|
410
|
+
});
|
|
564
411
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
412
|
+
if (documentation.servers) {
|
|
413
|
+
const servers = this.createServers(documentation.servers);
|
|
414
|
+
obj.servers = servers;
|
|
415
|
+
}
|
|
568
416
|
|
|
569
|
-
|
|
570
|
-
|
|
417
|
+
return { [method.toLowerCase()]: obj };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async createResponses(documentation) {
|
|
421
|
+
const responses = {};
|
|
422
|
+
for (const response of documentation.methodResponses) {
|
|
423
|
+
const obj = {
|
|
424
|
+
description: response.responseBody.description || "",
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
this.currentStatusCode = response.statusCode;
|
|
428
|
+
|
|
429
|
+
obj.content = await this.createMediaTypeObject(
|
|
430
|
+
response.responseModels,
|
|
431
|
+
"responses"
|
|
432
|
+
).catch((err) => {
|
|
433
|
+
throw err;
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
if (response.responseHeaders) {
|
|
437
|
+
obj.headers = await this.createResponseHeaders(
|
|
438
|
+
response.responseHeaders
|
|
439
|
+
).catch((err) => {
|
|
440
|
+
throw err;
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
let owaspHeaders = {};
|
|
445
|
+
if (response.owasp) {
|
|
446
|
+
if (typeof response.owasp === "boolean") {
|
|
447
|
+
owaspHeaders = await this.createResponseHeaders(
|
|
448
|
+
oWASP.DEFAULT_OWASP_HEADERS
|
|
449
|
+
).catch((err) => {
|
|
450
|
+
throw err;
|
|
451
|
+
});
|
|
452
|
+
} else {
|
|
453
|
+
owaspHeaders = await this.createResponseHeaders(
|
|
454
|
+
oWASP.getHeaders(response.owasp)
|
|
455
|
+
).catch((err) => {
|
|
456
|
+
throw err;
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const corsHeaders = await this.corsHeaders().catch((err) => {
|
|
462
|
+
throw err;
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const addHeaders = (headers) => {
|
|
466
|
+
for (const key in headers) {
|
|
467
|
+
if (!(key in obj.headers) && (obj.headers[key] = {})) {
|
|
468
|
+
obj.headers[key] = headers[key];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
if (obj.headers) {
|
|
474
|
+
addHeaders(corsHeaders);
|
|
475
|
+
addHeaders(owaspHeaders);
|
|
476
|
+
} else {
|
|
477
|
+
if (Object.keys(corsHeaders).length) {
|
|
478
|
+
obj.headers = corsHeaders;
|
|
479
|
+
addHeaders(owaspHeaders);
|
|
480
|
+
} else {
|
|
481
|
+
obj.headers = owaspHeaders;
|
|
571
482
|
}
|
|
483
|
+
}
|
|
572
484
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const schema = (models[contentKey]?.schema) ? models[contentKey].schema : models[contentKey]
|
|
577
|
-
const name = (models[contentKey]?.name) ? models[contentKey].name : uuid()
|
|
578
|
-
const schemaRef = await this.schemaHandler.createSchema(name, schema)
|
|
579
|
-
.catch(err => {
|
|
580
|
-
throw err
|
|
581
|
-
})
|
|
582
|
-
|
|
583
|
-
obj.schema = {
|
|
584
|
-
$ref: schemaRef
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
Object.assign(mediaTypeObj, { [contentKey]: obj })
|
|
588
|
-
}
|
|
589
|
-
}
|
|
485
|
+
if (response.links) {
|
|
486
|
+
obj.links = this.createLinks(response.links);
|
|
487
|
+
}
|
|
590
488
|
|
|
591
|
-
|
|
489
|
+
Object.assign(responses, { [response.statusCode]: obj });
|
|
592
490
|
}
|
|
593
491
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
492
|
+
return responses;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async corsHeaders() {
|
|
496
|
+
let headers = {};
|
|
497
|
+
if (this.currentEvent?.cors === true) {
|
|
498
|
+
headers = await this.createResponseHeaders(
|
|
499
|
+
this.DEFAULT_CORS_HEADERS
|
|
500
|
+
).catch((err) => {
|
|
501
|
+
throw err;
|
|
502
|
+
});
|
|
503
|
+
} else if (this.currentEvent.cors) {
|
|
504
|
+
const newHeaders = {};
|
|
505
|
+
for (const key of Object.keys(this.DEFAULT_CORS_HEADERS)) {
|
|
506
|
+
if (
|
|
507
|
+
key === "Access-Control-Allow-Credentials" &&
|
|
508
|
+
(this.currentEvent.cors.allowCredentials === undefined ||
|
|
509
|
+
this.currentEvent.cors?.allowCredentials === false)
|
|
510
|
+
) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const obj = JSON.parse(JSON.stringify(this.DEFAULT_CORS_HEADERS[key]));
|
|
515
|
+
|
|
516
|
+
if (key === "Access-Control-Allow-Origin") {
|
|
517
|
+
if (
|
|
518
|
+
this.currentEvent.cors?.origins ||
|
|
519
|
+
this.currentEvent.cors?.origin
|
|
520
|
+
) {
|
|
521
|
+
obj.schema.example =
|
|
522
|
+
this.currentEvent.cors?.origins?.toString() ||
|
|
523
|
+
this.currentEvent.cors?.origin?.toString();
|
|
524
|
+
} else if (this.currentEvent.cors?.allowedOrigins) {
|
|
525
|
+
obj.schema.example =
|
|
526
|
+
this.currentEvent.cors.allowedOrigins.toString();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
Object.assign(newHeaders, { [key]: obj });
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
headers = await this.createResponseHeaders(newHeaders).catch((err) => {
|
|
534
|
+
throw err;
|
|
535
|
+
});
|
|
536
|
+
}
|
|
614
537
|
|
|
615
|
-
|
|
616
|
-
|
|
538
|
+
return headers;
|
|
539
|
+
}
|
|
617
540
|
|
|
618
|
-
|
|
619
|
-
|
|
541
|
+
async createResponseHeaders(headers) {
|
|
542
|
+
const obj = {};
|
|
620
543
|
|
|
621
|
-
|
|
622
|
-
|
|
544
|
+
for (const header of Object.keys(headers)) {
|
|
545
|
+
const newHeader = {};
|
|
546
|
+
newHeader.description = headers[header].description || "";
|
|
623
547
|
|
|
624
|
-
|
|
625
|
-
|
|
548
|
+
if (headers[header].schema) {
|
|
549
|
+
const schemaRef = await this.schemaHandler
|
|
550
|
+
.createSchema(header, headers[header].schema)
|
|
551
|
+
.catch((err) => {
|
|
552
|
+
throw err;
|
|
553
|
+
});
|
|
554
|
+
newHeader.schema = {
|
|
555
|
+
$ref: schemaRef,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
626
558
|
|
|
627
|
-
|
|
628
|
-
const schemaRef = await this.schemaHandler.createSchema(param.name, param.schema)
|
|
629
|
-
.catch(err => {
|
|
630
|
-
throw err
|
|
631
|
-
})
|
|
632
|
-
obj.schema = {
|
|
633
|
-
$ref: schemaRef
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
params.push(obj)
|
|
638
|
-
}
|
|
639
|
-
return params;
|
|
559
|
+
Object.assign(obj, { [header]: newHeader });
|
|
640
560
|
}
|
|
641
561
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
562
|
+
return obj;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async createRequestBody(requestBodyDetails) {
|
|
566
|
+
const obj = {
|
|
567
|
+
description: requestBodyDetails.description,
|
|
568
|
+
required: requestBodyDetails.required || false,
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
obj.content = await this.createMediaTypeObject(
|
|
572
|
+
requestBodyDetails.models
|
|
573
|
+
).catch((err) => {
|
|
574
|
+
throw err;
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
return obj;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async createMediaTypeObject(models, type) {
|
|
581
|
+
const mediaTypeObj = {};
|
|
582
|
+
for (const mediaTypeDocumentation of this.schemaHandler.models) {
|
|
583
|
+
if (models === undefined || models === null) {
|
|
584
|
+
throw new Error(
|
|
585
|
+
`${this.currentFunctionName} is missing a Response Model for statusCode ${this.currentStatusCode}`
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (Object.values(models).includes(mediaTypeDocumentation.name)) {
|
|
590
|
+
let contentKey = "";
|
|
591
|
+
for (const [key, value] of Object.entries(models)) {
|
|
592
|
+
if (value === mediaTypeDocumentation.name) contentKey = key;
|
|
593
|
+
}
|
|
594
|
+
const obj = {};
|
|
595
|
+
|
|
596
|
+
let schema;
|
|
597
|
+
if (mediaTypeDocumentation?.content) {
|
|
598
|
+
if (mediaTypeDocumentation.content[contentKey]?.example)
|
|
599
|
+
obj.example = mediaTypeDocumentation.content[contentKey].example;
|
|
600
|
+
|
|
601
|
+
if (mediaTypeDocumentation.content[contentKey]?.examples)
|
|
602
|
+
obj.examples = this.createExamples(
|
|
603
|
+
mediaTypeDocumentation.content[contentKey].examples
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
schema = mediaTypeDocumentation.content[contentKey].schema;
|
|
607
|
+
} else if (
|
|
608
|
+
mediaTypeDocumentation?.contentType &&
|
|
609
|
+
mediaTypeDocumentation.schema
|
|
610
|
+
) {
|
|
611
|
+
if (mediaTypeDocumentation?.example)
|
|
612
|
+
obj.example = mediaTypeDocumentation.example;
|
|
613
|
+
|
|
614
|
+
if (mediaTypeDocumentation?.examples)
|
|
615
|
+
obj.examples = this.createExamples(mediaTypeDocumentation.examples);
|
|
616
|
+
|
|
617
|
+
schema = mediaTypeDocumentation.schema;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const schemaRef = await this.schemaHandler
|
|
621
|
+
.createSchema(mediaTypeDocumentation.name)
|
|
622
|
+
.catch((err) => {
|
|
623
|
+
throw err;
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
obj.schema = {
|
|
627
|
+
$ref: schemaRef,
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
Object.assign(mediaTypeObj, { [contentKey]: obj });
|
|
631
|
+
}
|
|
662
632
|
}
|
|
663
633
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
const openIdConnectScheme = this.createOpenIDConnectScheme(securityScheme)
|
|
687
|
-
schema.type = 'openIdConnect'
|
|
688
|
-
Object.assign(schema, openIdConnectScheme)
|
|
689
|
-
break;
|
|
690
|
-
|
|
691
|
-
case 'oauth2':
|
|
692
|
-
const oAuth2Scheme = this.createOAuth2Scheme(securityScheme)
|
|
693
|
-
schema.type = 'oauth2'
|
|
694
|
-
Object.assign(schema, oAuth2Scheme)
|
|
695
|
-
break;
|
|
696
|
-
}
|
|
634
|
+
if (Object.keys(mediaTypeObj).length === 0) {
|
|
635
|
+
for (const contentKey of Object.keys(models)) {
|
|
636
|
+
const obj = {};
|
|
637
|
+
const schema = models[contentKey]?.schema
|
|
638
|
+
? models[contentKey].schema
|
|
639
|
+
: models[contentKey];
|
|
640
|
+
const name = models[contentKey]?.name
|
|
641
|
+
? models[contentKey].name
|
|
642
|
+
: uuid();
|
|
643
|
+
const schemaRef = await this.schemaHandler
|
|
644
|
+
.createSchema(name, schema)
|
|
645
|
+
.catch((err) => {
|
|
646
|
+
throw err;
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
obj.schema = {
|
|
650
|
+
$ref: schemaRef,
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
Object.assign(mediaTypeObj, { [contentKey]: obj });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
697
656
|
|
|
698
|
-
|
|
699
|
-
|
|
657
|
+
return mediaTypeObj;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async createParamObject(paramIn, documentation) {
|
|
661
|
+
const params = [];
|
|
662
|
+
for (const param of documentation[`${paramIn}Params`]) {
|
|
663
|
+
const obj = {
|
|
664
|
+
name: param.name,
|
|
665
|
+
in: paramIn,
|
|
666
|
+
description: param.description || "",
|
|
667
|
+
required: paramIn === "path" ? true : param.required || false,
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
if (Object.keys(param).includes("deprecated")) {
|
|
671
|
+
obj.deprecated = param.deprecated;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (
|
|
675
|
+
paramIn === "query" &&
|
|
676
|
+
Object.keys(param).includes("allowEmptyValue")
|
|
677
|
+
) {
|
|
678
|
+
obj.allowEmptyValue = param.allowEmptyValue;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (param.style) obj.style = param.style;
|
|
682
|
+
|
|
683
|
+
if (Object.keys(param).includes("explode")) obj.explode = param.explode;
|
|
684
|
+
|
|
685
|
+
if (paramIn === "query" && param.allowReserved)
|
|
686
|
+
obj.allowReserved = param.allowReserved;
|
|
687
|
+
|
|
688
|
+
if (param.example) obj.example = param.example;
|
|
689
|
+
|
|
690
|
+
if (param.examples) obj.examples = this.createExamples(param.examples);
|
|
691
|
+
|
|
692
|
+
if (param.schema) {
|
|
693
|
+
const schemaRef = await this.schemaHandler
|
|
694
|
+
.createSchema(param.name, param.schema)
|
|
695
|
+
.catch((err) => {
|
|
696
|
+
throw err;
|
|
697
|
+
});
|
|
698
|
+
obj.schema = {
|
|
699
|
+
$ref: schemaRef,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
params.push(obj);
|
|
700
704
|
}
|
|
705
|
+
return params;
|
|
706
|
+
}
|
|
701
707
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
throw new Error('Security Scheme for "apiKey" requires the name of the header, query or cookie parameter to be used')
|
|
708
|
+
createLinks(links) {
|
|
709
|
+
const linksObj = {};
|
|
710
|
+
for (const link in links) {
|
|
711
|
+
const linkObj = links[link];
|
|
712
|
+
const obj = {};
|
|
708
713
|
|
|
709
|
-
|
|
710
|
-
schema.in = securitySchema.in
|
|
711
|
-
else
|
|
712
|
-
throw new Error('Security Scheme for "apiKey" requires the location of the API key: header, query or cookie parameter')
|
|
714
|
+
obj.operationId = linkObj.operation;
|
|
713
715
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
+
if (linkObj.description) {
|
|
717
|
+
obj.description = linkObj.description;
|
|
718
|
+
}
|
|
716
719
|
|
|
717
|
-
|
|
718
|
-
|
|
720
|
+
if (linkObj.server) {
|
|
721
|
+
obj.server = this.createServers(linkObj.server);
|
|
722
|
+
}
|
|
719
723
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
throw new Error('Security Scheme for "http" requires scheme')
|
|
724
|
+
if (linkObj.parameters) {
|
|
725
|
+
obj.parameters = linkObj.parameters;
|
|
726
|
+
}
|
|
724
727
|
|
|
725
|
-
|
|
726
|
-
|
|
728
|
+
if (linkObj.requestBody) {
|
|
729
|
+
obj.requestBody = linkObj.requestBody;
|
|
730
|
+
}
|
|
727
731
|
|
|
728
|
-
|
|
732
|
+
Object.assign(linksObj, { [link]: obj });
|
|
729
733
|
}
|
|
730
734
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
735
|
+
return linksObj;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
addToComponents(type, schema, name) {
|
|
739
|
+
const schemaObj = {
|
|
740
|
+
[name]: schema,
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
let newName = name;
|
|
744
|
+
|
|
745
|
+
if (this.openAPI?.components) {
|
|
746
|
+
if (this.openAPI.components[type]) {
|
|
747
|
+
if (
|
|
748
|
+
this.openAPI.components[type][name] &&
|
|
749
|
+
isEqual(schemaObj[name], this.openAPI.components[type][name]) ===
|
|
750
|
+
false
|
|
751
|
+
) {
|
|
752
|
+
delete schemaObj[name];
|
|
753
|
+
newName = `${name}-${uuid()}`;
|
|
754
|
+
schemaObj[newName] = schema;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
Object.assign(this.openAPI.components[type], schemaObj);
|
|
758
|
+
} else {
|
|
759
|
+
Object.assign(this.openAPI.components, { [type]: schemaObj });
|
|
760
|
+
}
|
|
761
|
+
} else {
|
|
762
|
+
const components = {
|
|
763
|
+
components: {
|
|
764
|
+
[type]: schemaObj,
|
|
765
|
+
},
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
Object.assign(this.openAPI, components);
|
|
739
769
|
}
|
|
740
770
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
771
|
+
return newName;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
createSecuritySchemes(securitySchemes) {
|
|
775
|
+
for (const scheme of Object.keys(securitySchemes)) {
|
|
776
|
+
const securityScheme = securitySchemes[scheme];
|
|
777
|
+
const schema = {};
|
|
778
|
+
|
|
779
|
+
if (securityScheme.description)
|
|
780
|
+
schema.description = securityScheme.description;
|
|
781
|
+
|
|
782
|
+
switch (securityScheme.type.toLowerCase()) {
|
|
783
|
+
case "apikey":
|
|
784
|
+
const apiKeyScheme = this.createAPIKeyScheme(securityScheme);
|
|
785
|
+
schema.type = "apiKey";
|
|
786
|
+
Object.assign(schema, apiKeyScheme);
|
|
787
|
+
break;
|
|
788
|
+
|
|
789
|
+
case "http":
|
|
790
|
+
const HTTPScheme = this.createHTTPScheme(securityScheme);
|
|
791
|
+
schema.type = "http";
|
|
792
|
+
Object.assign(schema, HTTPScheme);
|
|
793
|
+
break;
|
|
794
|
+
|
|
795
|
+
case "openidconnect":
|
|
796
|
+
const openIdConnectScheme =
|
|
797
|
+
this.createOpenIDConnectScheme(securityScheme);
|
|
798
|
+
schema.type = "openIdConnect";
|
|
799
|
+
Object.assign(schema, openIdConnectScheme);
|
|
800
|
+
break;
|
|
801
|
+
|
|
802
|
+
case "oauth2":
|
|
803
|
+
const oAuth2Scheme = this.createOAuth2Scheme(securityScheme);
|
|
804
|
+
schema.type = "oauth2";
|
|
805
|
+
Object.assign(schema, oAuth2Scheme);
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
this.addToComponents(this.componentTypes.securitySchemes, schema, scheme);
|
|
750
810
|
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
createAPIKeyScheme(securitySchema) {
|
|
814
|
+
const schema = {};
|
|
815
|
+
if (securitySchema.name) schema.name = securitySchema.name;
|
|
816
|
+
else
|
|
817
|
+
throw new Error(
|
|
818
|
+
'Security Scheme for "apiKey" requires the name of the header, query or cookie parameter to be used'
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
if (securitySchema.in) schema.in = securitySchema.in;
|
|
822
|
+
else
|
|
823
|
+
throw new Error(
|
|
824
|
+
'Security Scheme for "apiKey" requires the location of the API key: header, query or cookie parameter'
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
return schema;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
createHTTPScheme(securitySchema) {
|
|
831
|
+
const schema = {};
|
|
832
|
+
|
|
833
|
+
if (securitySchema.scheme) schema.scheme = securitySchema.scheme;
|
|
834
|
+
else throw new Error('Security Scheme for "http" requires scheme');
|
|
835
|
+
|
|
836
|
+
if (securitySchema.bearerFormat)
|
|
837
|
+
schema.bearerFormat = securitySchema.bearerFormat;
|
|
838
|
+
|
|
839
|
+
return schema;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
createOpenIDConnectScheme(securitySchema) {
|
|
843
|
+
const schema = {};
|
|
844
|
+
if (securitySchema.openIdConnectUrl)
|
|
845
|
+
schema.openIdConnectUrl = securitySchema.openIdConnectUrl;
|
|
846
|
+
else
|
|
847
|
+
throw new Error(
|
|
848
|
+
'Security Scheme for "openIdConnect" requires openIdConnectUrl'
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
return schema;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
createOAuth2Scheme(securitySchema) {
|
|
855
|
+
const schema = {};
|
|
856
|
+
if (securitySchema.flows) {
|
|
857
|
+
const flows = this.createOAuthFlows(securitySchema.flows);
|
|
858
|
+
Object.assign(schema, { flows: flows });
|
|
859
|
+
} else throw new Error('Security Scheme for "oauth2" requires flows');
|
|
860
|
+
|
|
861
|
+
return schema;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
createOAuthFlows(flows) {
|
|
865
|
+
const obj = {};
|
|
866
|
+
for (const flow of Object.keys(flows)) {
|
|
867
|
+
const schema = {};
|
|
868
|
+
if (["implicit", "authorizationCode"].includes(flow))
|
|
869
|
+
if (flows[flow].authorizationUrl)
|
|
870
|
+
schema.authorizationUrl = flows[flow].authorizationUrl;
|
|
871
|
+
else
|
|
872
|
+
throw new Error(`oAuth2 ${flow} flow requires an authorizationUrl`);
|
|
751
873
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
schema.authorizationUrl = flows[flow].authorizationUrl
|
|
759
|
-
else
|
|
760
|
-
throw new Error(`oAuth2 ${flow} flow requires an authorizationUrl`)
|
|
761
|
-
|
|
762
|
-
if (['password', 'clientCredentials', 'authorizationCode'].includes(flow)) {
|
|
763
|
-
if (flows[flow].tokenUrl)
|
|
764
|
-
schema.tokenUrl = flows[flow].tokenUrl
|
|
765
|
-
else
|
|
766
|
-
throw new Error(`oAuth2 ${flow} flow requires a tokenUrl`)
|
|
767
|
-
}
|
|
874
|
+
if (
|
|
875
|
+
["password", "clientCredentials", "authorizationCode"].includes(flow)
|
|
876
|
+
) {
|
|
877
|
+
if (flows[flow].tokenUrl) schema.tokenUrl = flows[flow].tokenUrl;
|
|
878
|
+
else throw new Error(`oAuth2 ${flow} flow requires a tokenUrl`);
|
|
879
|
+
}
|
|
768
880
|
|
|
769
|
-
|
|
770
|
-
schema.refreshUrl = flows[flow].refreshUrl
|
|
881
|
+
if (flows[flow].refreshUrl) schema.refreshUrl = flows[flow].refreshUrl;
|
|
771
882
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
else
|
|
775
|
-
throw new Error(`oAuth2 ${flow} flow requires scopes`)
|
|
883
|
+
if (flows[flow].scopes) schema.scopes = flows[flow].scopes;
|
|
884
|
+
else throw new Error(`oAuth2 ${flow} flow requires scopes`);
|
|
776
885
|
|
|
777
|
-
|
|
778
|
-
}
|
|
779
|
-
return obj
|
|
886
|
+
Object.assign(obj, { [flow]: schema });
|
|
780
887
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
888
|
+
return obj;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
createExamples(examples) {
|
|
892
|
+
const examplesObj = {};
|
|
893
|
+
|
|
894
|
+
for (const example of examples) {
|
|
895
|
+
const { name, ...partialExample } = example;
|
|
896
|
+
const componentName = this.addToComponents(
|
|
897
|
+
"examples",
|
|
898
|
+
partialExample,
|
|
899
|
+
example.name
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
Object.assign(examplesObj, {
|
|
903
|
+
[example.name]: { $ref: `#/components/examples/${componentName}` },
|
|
904
|
+
});
|
|
791
905
|
}
|
|
792
906
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
event
|
|
815
|
-
}
|
|
816
|
-
})
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
async validate() {
|
|
820
|
-
return await validator.validateInner(this.openAPI, {})
|
|
821
|
-
.catch(err => {
|
|
822
|
-
throw err
|
|
823
|
-
})
|
|
907
|
+
return examplesObj;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
cleanupLinks() {
|
|
911
|
+
for (const path of Object.keys(this.openAPI.paths)) {
|
|
912
|
+
for (const [name, value] of Object.entries(this.openAPI.paths[path])) {
|
|
913
|
+
for (const [statusCode, responseObj] of Object.entries(
|
|
914
|
+
value.responses
|
|
915
|
+
)) {
|
|
916
|
+
if (responseObj.links) {
|
|
917
|
+
for (const [linkName, linkObj] of Object.entries(
|
|
918
|
+
responseObj.links
|
|
919
|
+
)) {
|
|
920
|
+
const opId = linkObj.operationId;
|
|
921
|
+
if (this.functionMap[opId]) {
|
|
922
|
+
linkObj.operationId = this.functionMap[opId][0];
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
824
928
|
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
getHTTPFunctions() {
|
|
932
|
+
const isHttpFunction = (funcType) => {
|
|
933
|
+
const keys = Object.keys(funcType);
|
|
934
|
+
if (
|
|
935
|
+
keys.includes(this.httpKeys.http) ||
|
|
936
|
+
keys.includes(this.httpKeys.httpAPI)
|
|
937
|
+
)
|
|
938
|
+
return true;
|
|
939
|
+
};
|
|
940
|
+
const functionNames = this.serverless.service.getAllFunctions();
|
|
941
|
+
|
|
942
|
+
return functionNames
|
|
943
|
+
.map((functionName) => {
|
|
944
|
+
return this.serverless.service.getFunction(functionName);
|
|
945
|
+
})
|
|
946
|
+
.filter((functionType) => {
|
|
947
|
+
if (functionType?.events.some(isHttpFunction)) return functionType;
|
|
948
|
+
})
|
|
949
|
+
.map((functionType) => {
|
|
950
|
+
const event = functionType.events.filter(isHttpFunction);
|
|
951
|
+
const name = functionType.name.split(
|
|
952
|
+
`${this.serverless.service.service}-${this.serverless.service.provider.stage}-`
|
|
953
|
+
)[1];
|
|
954
|
+
|
|
955
|
+
Object.assign(this.functionMap, {
|
|
956
|
+
[name]: [],
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
return {
|
|
960
|
+
operationName: name,
|
|
961
|
+
functionInfo: functionType,
|
|
962
|
+
handler: functionType.handler,
|
|
963
|
+
name: functionType.name,
|
|
964
|
+
event,
|
|
965
|
+
};
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
async validate() {
|
|
970
|
+
return await validator.validateInner(this.openAPI, {}).catch((err) => {
|
|
971
|
+
throw err;
|
|
972
|
+
});
|
|
973
|
+
}
|
|
825
974
|
}
|
|
826
975
|
|
|
827
|
-
module.exports = DefinitionGenerator
|
|
976
|
+
module.exports = DefinitionGenerator;
|