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