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.
@@ -1,827 +1,976 @@
1
- 'use strict'
1
+ "use strict";
2
2
 
3
- const path = require('path')
3
+ const path = require("path");
4
4
 
5
- const { v4: uuid } = require('uuid')
6
- const validator = require('oas-validator');
5
+ const isEqual = require("lodash.isequal");
6
+ const validator = require("oas-validator");
7
+ const { v4: uuid } = require("uuid");
7
8
 
8
- const SchemaHandler = require('./schemaHandler')
9
- const oWASP = require('./owasp')
9
+ const SchemaHandler = require("./schemaHandler");
10
+ const oWASP = require("./owasp");
10
11
 
11
12
  class DefinitionGenerator {
12
- constructor(serverless, options = {}) {
13
- this.version = serverless?.processedInput?.options?.openApiVersion || '3.0.0'
14
-
15
- this.serverless = serverless
16
- this.httpKeys = {
17
- http: 'http',
18
- httpAPI: 'httpApi',
19
- }
20
-
21
- this.componentsSchemas = {
22
- requestBody: 'requestBodies',
23
- responses: 'responses',
24
- }
25
-
26
- this.openAPI = {
27
- openapi: this.version,
28
- components: {
29
- schemas: {}
30
- }
31
- }
32
-
33
- this.schemaHandler = new SchemaHandler(serverless, this.openAPI)
34
-
35
- this.operationIds = []
36
- this.schemaIDs = []
37
-
38
- this.componentTypes = {
39
- schemas: 'schemas',
40
- securitySchemes: 'securitySchemes'
41
- }
42
-
43
- this.DEFAULT_CORS_HEADERS = {
44
- 'Access-Control-Allow-Origin': {
45
- description: '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)',
46
- schema: {
47
- type: 'string',
48
- default: '*',
49
- example: 'https://developer.mozilla.org'
50
- }
51
- },
52
- 'Access-Control-Allow-Credentials': {
53
- 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)`,
54
- schema: {
55
- type: 'boolean',
56
- default: true
57
- }
58
- }
59
- }
60
-
61
- try {
62
- this.refParserOptions = require(path.resolve('options', 'ref-parser.js'))
63
- } catch (err) {
64
- this.refParserOptions = {}
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
- async parse() {
70
- this.createInfo()
71
-
72
- await oWASP.getLatest()
74
+ async parse() {
75
+ this.createInfo();
73
76
 
74
- await this.schemaHandler.addModelsToOpenAPI()
75
- .catch(err => {
76
- throw err
77
- })
77
+ await oWASP.getLatest();
78
78
 
79
- if (this.serverless.service.custom.documentation.securitySchemes) {
80
- this.createSecuritySchemes(this.serverless.service.custom.documentation.securitySchemes)
79
+ await this.schemaHandler.addModelsToOpenAPI().catch((err) => {
80
+ throw err;
81
+ });
81
82
 
82
- if (this.serverless.service.custom.documentation.security) {
83
- this.openAPI.security = this.serverless.service.custom.documentation.security
84
- }
85
- }
83
+ if (this.serverless.service.custom.documentation.securitySchemes) {
84
+ this.createSecuritySchemes(
85
+ this.serverless.service.custom.documentation.securitySchemes
86
+ );
86
87
 
87
- await this.createPaths()
88
- .catch(err => {
89
- throw err
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
- createInfo() {
108
- const service = this.serverless.service
109
- const documentation = this.serverless.service.custom.documentation;
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
- if (documentation.contact.url)
125
- contactObj.url = documentation.contact.url
98
+ this.cleanupLinks();
126
99
 
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
- }
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
- for (const key of Object.keys(documentation)) {
142
- if (/^[x\-]/i.test(key)) {
143
- Object.assign(info, { [key]: documentation[key] })
144
- }
145
- }
107
+ if (this.serverless.service.custom.documentation.tags) {
108
+ this.createTags();
109
+ }
146
110
 
147
- Object.assign(this.openAPI, { info })
148
- }
149
-
150
- async createPaths() {
151
- const paths = {}
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
- createServers(servers) {
203
- const serverDoc = servers
204
- const newServers = []
119
+ createInfo() {
120
+ const service = this.serverless.service;
121
+ const documentation = this.serverless.service.custom.documentation;
205
122
 
206
- if (Array.isArray(serverDoc)) {
207
- for (const server of serverDoc) {
208
- const obj = {
209
- url: server.url,
210
- }
123
+ const info = {
124
+ title: documentation?.title || service.service,
125
+ description: documentation?.description || "",
126
+ version: documentation?.version || uuid(),
127
+ };
211
128
 
212
- if (server.description) {
213
- obj.description = server.description
214
- }
129
+ if (documentation.termsOfService)
130
+ info.termsOfService = documentation.termsOfService;
215
131
 
216
- if (server.variables) {
217
- obj.variables = server.variables
218
- }
132
+ if (documentation.contact) {
133
+ const contactObj = {};
134
+ contactObj.name = documentation.contact.name || "";
219
135
 
220
- newServers.push(obj)
221
- }
222
- } else {
223
- const obj = {
224
- url: servers.url,
225
- }
136
+ if (documentation.contact.url) contactObj.url = documentation.contact.url;
226
137
 
227
- if (servers.description) {
228
- obj.description = servers.description
229
- }
138
+ contactObj.email = documentation.contact.email || "";
139
+ Object.assign(info, { contact: contactObj });
140
+ }
230
141
 
231
- if (servers.variables) {
232
- obj.variables = servers.variables
233
- }
142
+ if (documentation.license && documentation.license.name) {
143
+ const licenseObj = {};
144
+ licenseObj.name = documentation.license.name || "";
234
145
 
235
- newServers.push(obj)
236
- }
146
+ if (documentation.license.url)
147
+ licenseObj.url = documentation.license.url || "";
237
148
 
238
- return newServers
149
+ Object.assign(info, { license: licenseObj });
239
150
  }
240
151
 
241
- createExternalDocumentation(docs) {
242
- return { ...docs }
243
- // const documentation = this.serverless.service.custom.documentation
244
- // if (documentation.externalDocumentation) {
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
- createTags() {
251
- const tags = []
252
- for (const tag of this.serverless.service.custom.documentation.tags) {
253
- const obj = {
254
- name: tag.name,
255
- }
256
-
257
- if (tag.description) {
258
- obj.description = tag.description
259
- }
260
-
261
- if (tag.externalDocumentation) {
262
- obj.externalDocs = this.createExternalDocumentation(tag.externalDocumentation)
263
- }
264
- tags.push(obj)
265
- }
266
- Object.assign(this.openAPI, { tags: tags })
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
- async createOperationObject(method, documentation, name = uuid()) {
270
- const obj = {
271
- summary: documentation.summary || '',
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
- 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
+ if (Array.isArray(serverDoc)) {
217
+ for (const server of serverDoc) {
218
+ const obj = {
219
+ url: server.url,
220
+ };
309
221
 
310
- if (documentation.externalDocumentation) {
311
- obj.externalDocs = documentation.externalDocumentation
222
+ if (server.description) {
223
+ obj.description = server.description;
312
224
  }
313
225
 
314
- if (Object.keys(documentation).includes('security')) {
315
- obj.security = documentation.security
226
+ if (server.variables) {
227
+ obj.variables = server.variables;
316
228
  }
317
229
 
318
- if (this.currentEvent?.private && this.currentEvent.private === true) {
319
- let apiKeyName = 'x-api-key'
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
- }
230
+ newServers.push(obj);
231
+ }
232
+ } else {
233
+ const obj = {
234
+ url: servers.url,
235
+ };
329
236
 
330
- if (hasXAPIKey === false) {
331
- this.createSecuritySchemes({[apiKeyName]: {type: 'apiKey', name: apiKeyName, in: 'header'}})
332
- }
237
+ if (servers.description) {
238
+ obj.description = servers.description;
239
+ }
333
240
 
334
- if (obj.security) {
335
- obj.security.push({[apiKeyName]: []})
336
- } else {
337
- obj.security = [{[apiKeyName]: []}]
338
- }
339
- }
241
+ if (servers.variables) {
242
+ obj.variables = servers.variables;
243
+ }
340
244
 
341
- if (Object.keys(documentation).includes('deprecated'))
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
- async createResponses(documentation) {
386
- const responses = {}
387
- for (const response of documentation.methodResponses) {
388
- const obj = {
389
- description: response.responseBody.description || '',
390
- }
391
-
392
- this.currentStatusCode = response.statusCode
393
-
394
- obj.content = await this.createMediaTypeObject(response.responseModels, 'responses')
395
- .catch(err => {
396
- throw err
397
- })
398
-
399
- if (response.responseHeaders) {
400
- obj.headers = await this.createResponseHeaders(response.responseHeaders)
401
- .catch(err => {
402
- throw err
403
- })
404
- }
405
-
406
- let owaspHeaders = {}
407
- if (response.owasp) {
408
- if (typeof response.owasp === 'boolean') {
409
- owaspHeaders = await this.createResponseHeaders(oWASP.DEFAULT_OWASP_HEADERS)
410
- .catch(err => {
411
- throw err
412
- })
413
- } else {
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
- async createResponseHeaders(headers) {
490
- const obj = {}
491
-
492
- for (const header of Object.keys(headers)) {
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
- async createRequestBody(requestBodyDetails) {
513
- const obj = {
514
- description: requestBodyDetails.description,
515
- required: requestBodyDetails.required || false
516
- }
517
-
518
- obj.content = await this.createMediaTypeObject(requestBodyDetails.models)
519
- .catch(err => {
520
- throw err
521
- })
522
-
523
- return obj
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
- async createMediaTypeObject(models, type) {
527
- const mediaTypeObj = {}
528
- for (const mediaTypeDocumentation of this.schemaHandler.models) {
529
- if (models === undefined || models === null) {
530
- throw new Error(`${this.currentFunctionName} is missing a Response Model for statusCode ${this.currentStatusCode}`)
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
- if (Object.values(models).includes(mediaTypeDocumentation.name)) {
534
- let contentKey = ''
535
- for (const [key, value] of Object.entries(models)) {
536
- if (value === mediaTypeDocumentation.name)
537
- contentKey = key;
538
- }
539
- const obj = {}
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
- let schema
542
- if (mediaTypeDocumentation?.content) {
543
- if (mediaTypeDocumentation.content[contentKey]?.example)
544
- obj.example = mediaTypeDocumentation.content[contentKey].example
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
- if (mediaTypeDocumentation.content[contentKey]?.examples)
547
- obj.examples = this.createExamples(mediaTypeDocumentation.content[contentKey].examples)
344
+ if (documentation.externalDocumentation) {
345
+ obj.externalDocs = documentation.externalDocumentation;
346
+ }
548
347
 
549
- schema = mediaTypeDocumentation.content[contentKey].schema
550
- } else if (mediaTypeDocumentation?.contentType && mediaTypeDocumentation.schema) {
551
- if (mediaTypeDocumentation?.example)
552
- obj.example = mediaTypeDocumentation.example
348
+ if (Object.keys(documentation).includes("security")) {
349
+ obj.security = documentation.security;
350
+ }
553
351
 
554
- if (mediaTypeDocumentation?.examples)
555
- obj.examples = this.createExamples(mediaTypeDocumentation.examples)
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
- schema = mediaTypeDocumentation.schema
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
- const schemaRef = await this.schemaHandler.createSchema(mediaTypeDocumentation.name)
561
- .catch(err => {
562
- throw err
563
- })
407
+ if (documentation.methodResponses)
408
+ obj.responses = await this.createResponses(documentation).catch((err) => {
409
+ throw err;
410
+ });
564
411
 
565
- obj.schema = {
566
- $ref: schemaRef
567
- }
412
+ if (documentation.servers) {
413
+ const servers = this.createServers(documentation.servers);
414
+ obj.servers = servers;
415
+ }
568
416
 
569
- Object.assign(mediaTypeObj, { [contentKey]: obj })
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
- if (Object.keys(mediaTypeObj).length === 0) {
574
- for (const contentKey of Object.keys(models)) {
575
- const obj = {}
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
- return mediaTypeObj
489
+ Object.assign(responses, { [response.statusCode]: obj });
592
490
  }
593
491
 
594
- async createParamObject(paramIn, documentation) {
595
- const params = []
596
- for (const param of documentation[`${paramIn}Params`]) {
597
- const obj = {
598
- name: param.name,
599
- in: paramIn,
600
- description: param.description || '',
601
- required: (paramIn === 'path') ? true : param.required || false,
602
- }
603
-
604
- if (Object.keys(param).includes('deprecated')) {
605
- obj.deprecated = param.deprecated
606
- }
607
-
608
- if (paramIn === 'query' && Object.keys(param).includes('allowEmptyValue')) {
609
- obj.allowEmptyValue = param.allowEmptyValue
610
- }
611
-
612
- if (param.style)
613
- obj.style = param.style
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
- if (Object.keys(param).includes('explode'))
616
- obj.explode = param.explode
538
+ return headers;
539
+ }
617
540
 
618
- if (paramIn === 'query' && param.allowReserved)
619
- obj.allowReserved = param.allowReserved
541
+ async createResponseHeaders(headers) {
542
+ const obj = {};
620
543
 
621
- if (param.example)
622
- obj.example = param.example
544
+ for (const header of Object.keys(headers)) {
545
+ const newHeader = {};
546
+ newHeader.description = headers[header].description || "";
623
547
 
624
- if (param.examples)
625
- obj.examples = this.createExamples(param.examples)
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
- 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;
559
+ Object.assign(obj, { [header]: newHeader });
640
560
  }
641
561
 
642
- addToComponents(type, schema, name) {
643
- const schemaObj = {
644
- [name]: schema
645
- }
646
-
647
- if (this.openAPI?.components) {
648
- if (this.openAPI.components[type]) {
649
- Object.assign(this.openAPI.components[type], schemaObj)
650
- } else {
651
- Object.assign(this.openAPI.components, { [type]: schemaObj })
652
- }
653
- } else {
654
- const components = {
655
- components: {
656
- [type]: schemaObj
657
- }
658
- }
659
-
660
- Object.assign(this.openAPI, components)
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
- createSecuritySchemes(securitySchemes) {
665
- for (const scheme of Object.keys(securitySchemes)) {
666
- const securityScheme = securitySchemes[scheme]
667
- const schema = {}
668
-
669
- if (securityScheme.description)
670
- schema.description = securityScheme.description
671
-
672
- switch (securityScheme.type.toLowerCase()) {
673
- case 'apikey':
674
- const apiKeyScheme = this.createAPIKeyScheme(securityScheme)
675
- schema.type = 'apiKey'
676
- Object.assign(schema, apiKeyScheme)
677
- break;
678
-
679
- case 'http':
680
- const HTTPScheme = this.createHTTPScheme(securityScheme)
681
- schema.type = 'http'
682
- Object.assign(schema, HTTPScheme)
683
- break;
684
-
685
- case 'openidconnect':
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
- this.addToComponents(this.componentTypes.securitySchemes, schema, scheme)
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
- 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')
708
+ createLinks(links) {
709
+ const linksObj = {};
710
+ for (const link in links) {
711
+ const linkObj = links[link];
712
+ const obj = {};
708
713
 
709
- if (securitySchema.in)
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
- return schema
715
- }
716
+ if (linkObj.description) {
717
+ obj.description = linkObj.description;
718
+ }
716
719
 
717
- createHTTPScheme(securitySchema) {
718
- const schema = {}
720
+ if (linkObj.server) {
721
+ obj.server = this.createServers(linkObj.server);
722
+ }
719
723
 
720
- if (securitySchema.scheme)
721
- schema.scheme = securitySchema.scheme
722
- else
723
- throw new Error('Security Scheme for "http" requires scheme')
724
+ if (linkObj.parameters) {
725
+ obj.parameters = linkObj.parameters;
726
+ }
724
727
 
725
- if (securitySchema.bearerFormat)
726
- schema.bearerFormat = securitySchema.bearerFormat
728
+ if (linkObj.requestBody) {
729
+ obj.requestBody = linkObj.requestBody;
730
+ }
727
731
 
728
- return schema
732
+ Object.assign(linksObj, { [link]: obj });
729
733
  }
730
734
 
731
- createOpenIDConnectScheme(securitySchema) {
732
- const schema = {}
733
- if (securitySchema.openIdConnectUrl)
734
- schema.openIdConnectUrl = securitySchema.openIdConnectUrl
735
- else
736
- throw new Error('Security Scheme for "openIdConnect" requires openIdConnectUrl')
737
-
738
- return schema
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
- createOAuth2Scheme(securitySchema) {
742
- const schema = {}
743
- if (securitySchema.flows) {
744
- const flows = this.createOAuthFlows(securitySchema.flows)
745
- Object.assign(schema, { flows: flows })
746
- } else
747
- throw new Error('Security Scheme for "oauth2" requires flows')
748
-
749
- return schema
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
- createOAuthFlows(flows) {
753
- const obj = {}
754
- for (const flow of Object.keys(flows)) {
755
- const schema = {}
756
- if (["implicit", 'authorizationCode'].includes(flow))
757
- if (flows[flow].authorizationUrl)
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
- if (flows[flow].refreshUrl)
770
- schema.refreshUrl = flows[flow].refreshUrl
881
+ if (flows[flow].refreshUrl) schema.refreshUrl = flows[flow].refreshUrl;
771
882
 
772
- if (flows[flow].scopes)
773
- schema.scopes = flows[flow].scopes
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
- Object.assign(obj, { [flow]: schema })
778
- }
779
- return obj
886
+ Object.assign(obj, { [flow]: schema });
780
887
  }
781
-
782
- createExamples(examples) {
783
- const examplesObj = {}
784
-
785
- for (const example of examples) {
786
- Object.assign(examplesObj, { [example.name]: example })
787
- delete examplesObj[example.name].name
788
- }
789
-
790
- return examplesObj;
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
- getHTTPFunctions() {
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
- })
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;