serverless-openapi-documenter 0.0.70 → 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.
@@ -1,827 +1,902 @@
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.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
- async parse() {
70
- this.createInfo()
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
- await this.createPaths()
88
- .catch(err => {
89
- throw err
90
- })
74
+ await oWASP.getLatest();
91
75
 
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
- }
76
+ await this.schemaHandler.addModelsToOpenAPI().catch((err) => {
77
+ throw err;
78
+ });
96
79
 
97
- if (this.serverless.service.custom.documentation.tags) {
98
- this.createTags()
99
- }
80
+ if (this.serverless.service.custom.documentation.securitySchemes) {
81
+ this.createSecuritySchemes(
82
+ this.serverless.service.custom.documentation.securitySchemes
83
+ );
100
84
 
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
- }
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
- 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
- }
91
+ await this.createPaths().catch((err) => {
92
+ throw err;
93
+ });
116
94
 
117
- if (documentation.termsOfService)
118
- info.termsOfService = documentation.termsOfService
119
-
120
- if (documentation.contact) {
121
- const contactObj = {}
122
- contactObj.name = documentation.contact.name || ''
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
- Object.assign(this.openAPI, { info })
102
+ if (this.serverless.service.custom.documentation.tags) {
103
+ this.createTags();
148
104
  }
149
105
 
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 })
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
- createServers(servers) {
203
- const serverDoc = servers
204
- const newServers = []
114
+ createInfo() {
115
+ const service = this.serverless.service;
116
+ const documentation = this.serverless.service.custom.documentation;
205
117
 
206
- if (Array.isArray(serverDoc)) {
207
- for (const server of serverDoc) {
208
- const obj = {
209
- url: server.url,
210
- }
118
+ const info = {
119
+ title: documentation?.title || service.service,
120
+ description: documentation?.description || "",
121
+ version: documentation?.version || uuid(),
122
+ };
211
123
 
212
- if (server.description) {
213
- obj.description = server.description
214
- }
124
+ if (documentation.termsOfService)
125
+ info.termsOfService = documentation.termsOfService;
215
126
 
216
- if (server.variables) {
217
- obj.variables = server.variables
218
- }
127
+ if (documentation.contact) {
128
+ const contactObj = {};
129
+ contactObj.name = documentation.contact.name || "";
219
130
 
220
- newServers.push(obj)
221
- }
222
- } else {
223
- const obj = {
224
- url: servers.url,
225
- }
131
+ if (documentation.contact.url) contactObj.url = documentation.contact.url;
226
132
 
227
- if (servers.description) {
228
- obj.description = servers.description
229
- }
133
+ contactObj.email = documentation.contact.email || "";
134
+ Object.assign(info, { contact: contactObj });
135
+ }
230
136
 
231
- if (servers.variables) {
232
- obj.variables = servers.variables
233
- }
137
+ if (documentation.license && documentation.license.name) {
138
+ const licenseObj = {};
139
+ licenseObj.name = documentation.license.name || "";
234
140
 
235
- newServers.push(obj)
236
- }
141
+ if (documentation.license.url)
142
+ licenseObj.url = documentation.license.url || "";
237
143
 
238
- return newServers
144
+ Object.assign(info, { license: licenseObj });
239
145
  }
240
146
 
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
- // }
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
- 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 })
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
- 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
- }
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
- if (documentation.externalDocumentation) {
311
- obj.externalDocs = documentation.externalDocumentation
312
- }
220
+ if (Array.isArray(serverDoc)) {
221
+ for (const server of serverDoc) {
222
+ const obj = {
223
+ url: server.url,
224
+ };
313
225
 
314
- if (Object.keys(documentation).includes('security')) {
315
- obj.security = documentation.security
226
+ if (server.description) {
227
+ obj.description = server.description;
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
- }
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
- 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
- }
234
+ newServers.push(obj);
235
+ }
236
+ } else {
237
+ const obj = {
238
+ url: servers.url,
239
+ };
370
240
 
371
- if (documentation.methodResponses)
372
- obj.responses = await this.createResponses(documentation)
373
- .catch(err => {
374
- throw err
375
- })
241
+ if (servers.description) {
242
+ obj.description = servers.description;
243
+ }
376
244
 
377
- if (documentation.servers) {
378
- const servers = this.createServers(documentation.servers)
379
- obj.servers = servers
380
- }
245
+ if (servers.variables) {
246
+ obj.variables = servers.variables;
247
+ }
381
248
 
382
- return { [method.toLowerCase()]: obj }
249
+ newServers.push(obj);
383
250
  }
384
251
 
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
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
- 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;
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
- 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
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
- 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
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
- 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
- }
532
-
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 = {}
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
- 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
- }
334
+ if (documentation.externalDocumentation) {
335
+ obj.externalDocs = documentation.externalDocumentation;
336
+ }
590
337
 
591
- return mediaTypeObj
338
+ if (Object.keys(documentation).includes("security")) {
339
+ obj.security = documentation.security;
592
340
  }
593
341
 
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
614
-
615
- if (Object.keys(param).includes('explode'))
616
- obj.explode = param.explode
617
-
618
- if (paramIn === 'query' && param.allowReserved)
619
- obj.allowReserved = param.allowReserved
620
-
621
- if (param.example)
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
- addToComponents(type, schema, name) {
643
- const schemaObj = {
644
- [name]: schema
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
- 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
- }
397
+ if (documentation.methodResponses)
398
+ obj.responses = await this.createResponses(documentation).catch((err) => {
399
+ throw err;
400
+ });
659
401
 
660
- Object.assign(this.openAPI, components)
661
- }
402
+ if (documentation.servers) {
403
+ const servers = this.createServers(documentation.servers);
404
+ obj.servers = servers;
662
405
  }
663
406
 
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
- }
697
-
698
- this.addToComponents(this.componentTypes.securitySchemes, schema, scheme)
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
- 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')
475
+ Object.assign(responses, { [response.statusCode]: obj });
476
+ }
713
477
 
714
- return schema
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
- createHTTPScheme(securitySchema) {
718
- const schema = {}
524
+ return headers;
525
+ }
719
526
 
720
- if (securitySchema.scheme)
721
- schema.scheme = securitySchema.scheme
722
- else
723
- throw new Error('Security Scheme for "http" requires scheme')
527
+ async createResponseHeaders(headers) {
528
+ const obj = {};
724
529
 
725
- if (securitySchema.bearerFormat)
726
- schema.bearerFormat = securitySchema.bearerFormat
530
+ for (const header of Object.keys(headers)) {
531
+ const newHeader = {};
532
+ newHeader.description = headers[header].description || "";
727
533
 
728
- return schema
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
- 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')
545
+ Object.assign(obj, { [header]: newHeader });
546
+ }
737
547
 
738
- return schema
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
- 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')
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
- return schema
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
- 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
- }
768
-
769
- if (flows[flow].refreshUrl)
770
- schema.refreshUrl = flows[flow].refreshUrl
771
-
772
- if (flows[flow].scopes)
773
- schema.scopes = flows[flow].scopes
774
- else
775
- throw new Error(`oAuth2 ${flow} flow requires scopes`)
776
-
777
- Object.assign(obj, { [flow]: schema })
778
- }
779
- return obj
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
- createExamples(examples) {
783
- const examplesObj = {}
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
- for (const example of examples) {
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
- return examplesObj;
791
- }
839
+ if (flows[flow].scopes) schema.scopes = flows[flow].scopes;
840
+ else throw new Error(`oAuth2 ${flow} flow requires scopes`);
792
841
 
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
- })
842
+ Object.assign(obj, { [flow]: schema });
817
843
  }
818
-
819
- async validate() {
820
- return await validator.validateInner(this.openAPI, {})
821
- .catch(err => {
822
- throw err
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;