serverless-openapi-documenter 0.0.32 → 0.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -15
- package/package.json +3 -2
- package/src/definitionGenerator.js +191 -52
- package/test/unit/definitionGenerator.spec.js +544 -182
package/README.md
CHANGED
|
@@ -52,6 +52,16 @@ Options:
|
|
|
52
52
|
--postmanCollection -p Will generate a postman collection (from the generated openAPI documentation), in json only, if passed in. Default postman.json
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
### README Highlighted Reading
|
|
56
|
+
|
|
57
|
+
#### Security Details
|
|
58
|
+
* [Security](#securityschemes)
|
|
59
|
+
* [Security on All Operations](#security-on-each-operation)
|
|
60
|
+
* [Security Per Operation](#security)
|
|
61
|
+
#### Model Details
|
|
62
|
+
* [Models](#models)
|
|
63
|
+
* [Notes on Schemas](#notes-on-schemas)
|
|
64
|
+
|
|
55
65
|
### OpenAPI Mapping
|
|
56
66
|
|
|
57
67
|
| OpenAPI field | Serverless field |
|
|
@@ -59,15 +69,16 @@ Options:
|
|
|
59
69
|
| info.title | custom.documentation.title OR service |
|
|
60
70
|
| info.description | custom.documentation.description OR blank string |
|
|
61
71
|
| info.version | custom.documentation.version OR random v4 uuid if not provided |
|
|
62
|
-
| info.termsOfService | custom.documentation.termsOfService
|
|
63
|
-
| info.contact | custom.documentation.contact
|
|
64
|
-
| info.contact.name | custom.documentation.contact.name OR blank string
|
|
65
|
-
| info.contact.url | custom.documentation.contact.url if provided
|
|
66
|
-
| info.license | custom.documentation.license
|
|
67
|
-
| info.license.name | custom.documentation.license.name OR blank string
|
|
68
|
-
| info.license.url | custom.documentation.license.url if provided
|
|
72
|
+
| info.termsOfService | custom.documentation.termsOfService |
|
|
73
|
+
| info.contact | custom.documentation.contact |
|
|
74
|
+
| info.contact.name | custom.documentation.contact.name OR blank string |
|
|
75
|
+
| info.contact.url | custom.documentation.contact.url if provided |
|
|
76
|
+
| info.license | custom.documentation.license |
|
|
77
|
+
| info.license.name | custom.documentation.license.name OR blank string |
|
|
78
|
+
| info.license.url | custom.documentation.license.url if provided |
|
|
69
79
|
| externalDocs.description | custom.documentation.externalDocumentation.description |
|
|
70
80
|
| externalDocs.url | custom.documentation.externalDocumentation.url |
|
|
81
|
+
| security | custom.documentation.security |
|
|
71
82
|
| servers[].description | custom.documentation.servers.description |
|
|
72
83
|
| servers[].url | custom.documentation.servers.url |
|
|
73
84
|
| servers[].variables | custom.documentation.servers.variables |
|
|
@@ -89,6 +100,7 @@ Options:
|
|
|
89
100
|
| path[path].[operation].externalDocs.url | functions.functions.[http OR httpApi].documentation.externalDocumentation.url |
|
|
90
101
|
| path[path].[operation].servers[].description | functions.functions.[http OR httpApi].documentation.servers.description |
|
|
91
102
|
| path[path].[operation].servers[].url | functions.functions.[http OR httpApi].documentation.servers.url |
|
|
103
|
+
| path[path].[operation].security | functions.functions.[http OR httpApi].documentation.security |
|
|
92
104
|
| path[path].[operation].deprecated | functions.functions.[http OR httpApi].documentation.deprecated |
|
|
93
105
|
| path[path].[operation].parameters | functions.functions.[http OR httpApi].documentation.[path/query/cookie/header]Params |
|
|
94
106
|
| path[path].[operation].parameters.name | functions.functions.[http OR httpApi].documentation.[path/query/cookie/header]Params.name |
|
|
@@ -182,6 +194,7 @@ custom:
|
|
|
182
194
|
```
|
|
183
195
|
|
|
184
196
|
Name is required but `url` is optional and must be in the format of a url.
|
|
197
|
+
|
|
185
198
|
#### Extended Fields
|
|
186
199
|
|
|
187
200
|
You can also add extended fields to the documentation object:
|
|
@@ -219,6 +232,40 @@ functions:
|
|
|
219
232
|
|
|
220
233
|
For more info on `serverless.yml` syntax, see their docs.
|
|
221
234
|
|
|
235
|
+
#### securitySchemes
|
|
236
|
+
|
|
237
|
+
You can provide optional Security Schemes:
|
|
238
|
+
|
|
239
|
+
```yml
|
|
240
|
+
custom:
|
|
241
|
+
documentation:
|
|
242
|
+
securitySchemes:
|
|
243
|
+
my_api_key:
|
|
244
|
+
type: apiKey
|
|
245
|
+
name: api_key
|
|
246
|
+
in: header
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
It accepts all available Security Schemes and follows the specification: https://spec.openapis.org/oas/v3.0.3#security-scheme-object
|
|
250
|
+
|
|
251
|
+
#### Security on each operation
|
|
252
|
+
|
|
253
|
+
To apply an overall security scheme to all of your operations without having to add the documentation to each one, you can write it like:
|
|
254
|
+
|
|
255
|
+
```yml
|
|
256
|
+
custom:
|
|
257
|
+
documentation:
|
|
258
|
+
securitySchemes:
|
|
259
|
+
my_api_key:
|
|
260
|
+
type: apiKey
|
|
261
|
+
name: api_key
|
|
262
|
+
in: header
|
|
263
|
+
security:
|
|
264
|
+
- my_api_key: []
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
This will apply the requirement of each operation requiring your `my_api_key` security scheme, [you can override this](#security).
|
|
268
|
+
|
|
222
269
|
#### Models
|
|
223
270
|
|
|
224
271
|
There are two ways to write the Models. Models contain additional information that you can use to define schemas for endpoints. You must define the *content type* for each schema that you provide in the models.
|
|
@@ -304,20 +351,20 @@ custom:
|
|
|
304
351
|
content:
|
|
305
352
|
application/json:
|
|
306
353
|
schema: &ErrorItem
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
354
|
+
type: object
|
|
355
|
+
properties:
|
|
356
|
+
message:
|
|
357
|
+
type: string
|
|
358
|
+
code:
|
|
359
|
+
type: integer
|
|
313
360
|
|
|
314
361
|
- name: "PutDocumentResponse"
|
|
315
362
|
description: "PUT Document response model (external reference example)"
|
|
316
363
|
content:
|
|
317
364
|
application/json:
|
|
318
365
|
schema:
|
|
319
|
-
|
|
320
|
-
|
|
366
|
+
type: array
|
|
367
|
+
items: *ErrorItem
|
|
321
368
|
```
|
|
322
369
|
|
|
323
370
|
`&ErrorItem` in the above example creates a node anchor (&ErrorItem) to the `ErrorResponse` schema which then can be used in the `PutDocumentResponse` schema via the reference (*ErrorItem). The node anchor needs to be declared first before it can be used elsewhere via the reference, swapping the above example around would result in an error.
|
|
@@ -340,6 +387,7 @@ The `documentation` section of the event configuration can contain the following
|
|
|
340
387
|
* `pathParams`: a list of path parameters (see [pathParams](#pathparams) below)
|
|
341
388
|
* `cookieParams`: a list of cookie parameters (see [cookieParams](#cookieparams) below)
|
|
342
389
|
* `headerParams`: a list of headers (see [headerParams](#headerparams---request-headers) below)
|
|
390
|
+
* `security`: The security requirement to apply (see [security](#security) below)
|
|
343
391
|
* `methodResponses`: an array of response models and applicable status codes
|
|
344
392
|
* `statusCode`: applicable http status code (ie. 200/404/500 etc.)
|
|
345
393
|
* `responseBody`: contains description of the response
|
|
@@ -480,6 +528,62 @@ headerParams:
|
|
|
480
528
|
type: "string"
|
|
481
529
|
```
|
|
482
530
|
|
|
531
|
+
#### `security`
|
|
532
|
+
|
|
533
|
+
The `security` property allows you to specify the [Security Scheme](#securityschemes) to apply to the HTTP Request. If you have applied an `security` ([see Security on each operation](#security-on-each-operation)) then you can either leave this field off, or to override it with a different scheme you can write it like:
|
|
534
|
+
|
|
535
|
+
```yml
|
|
536
|
+
custom:
|
|
537
|
+
documentation:
|
|
538
|
+
securitySchemes:
|
|
539
|
+
my_api_key:
|
|
540
|
+
type: apiKey
|
|
541
|
+
name: api_key
|
|
542
|
+
in: header
|
|
543
|
+
petstore_auth:
|
|
544
|
+
type: oauth2
|
|
545
|
+
flows:
|
|
546
|
+
implicit:
|
|
547
|
+
authorizationUrl: https://example.com/api/oauth/dialog
|
|
548
|
+
scopes:
|
|
549
|
+
write:pets: modify pets in your account
|
|
550
|
+
read:pets: read your pets
|
|
551
|
+
security:
|
|
552
|
+
- my_api_key: []
|
|
553
|
+
|
|
554
|
+
functions:
|
|
555
|
+
getData:
|
|
556
|
+
events:
|
|
557
|
+
- http:
|
|
558
|
+
documentation:
|
|
559
|
+
security:
|
|
560
|
+
- petstore_auth:
|
|
561
|
+
- write:pets
|
|
562
|
+
- read:pets
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
If you have specified an `security` at the document root, but this HTTP Request should not apply any security schemes, you should set security to be an array with an empty object:
|
|
566
|
+
|
|
567
|
+
```yml
|
|
568
|
+
custom:
|
|
569
|
+
documentation:
|
|
570
|
+
securitySchemes:
|
|
571
|
+
my_api_key:
|
|
572
|
+
type: apiKey
|
|
573
|
+
name: api_key
|
|
574
|
+
in: header
|
|
575
|
+
security:
|
|
576
|
+
- my_api_key: []
|
|
577
|
+
|
|
578
|
+
functions:
|
|
579
|
+
getData:
|
|
580
|
+
events:
|
|
581
|
+
- http:
|
|
582
|
+
documentation:
|
|
583
|
+
security:
|
|
584
|
+
- {}
|
|
585
|
+
```
|
|
586
|
+
|
|
483
587
|
#### `requestModels`
|
|
484
588
|
|
|
485
589
|
The `requestModels` property allows you to define models for the HTTP Request of the function event. You can define a different model for each different `Content-Type`. You can define a reference to the relevant request model named in the `models` section of your configuration (see [Defining Models](#models) section).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverless-openapi-documenter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.40",
|
|
4
4
|
"description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"chalk": "^4.1.2",
|
|
33
33
|
"js-yaml": "^4.1.0",
|
|
34
34
|
"json-schema-for-openapi": "^0.3.1",
|
|
35
|
+
"lodash.isequal": "^4.5.0",
|
|
35
36
|
"oas-validator": "^5.0.8",
|
|
36
37
|
"openapi-to-postmanv2": "^4.4.1",
|
|
37
38
|
"swagger2openapi": "^7.0.8",
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"chai": "^4.3.7",
|
|
45
|
-
"mocha": "^10.
|
|
46
|
+
"mocha": "^10.2.0",
|
|
46
47
|
"sinon": "^15.0.0"
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -5,7 +5,8 @@ const path = require('path')
|
|
|
5
5
|
const { v4: uuid } = require('uuid')
|
|
6
6
|
const validator = require('oas-validator');
|
|
7
7
|
const SchemaConvertor = require('json-schema-for-openapi')
|
|
8
|
-
const $RefParser = require("@apidevtools/json-schema-ref-parser")
|
|
8
|
+
const $RefParser = require("@apidevtools/json-schema-ref-parser")
|
|
9
|
+
const isEqual = require('lodash.isequal')
|
|
9
10
|
|
|
10
11
|
class DefinitionGenerator {
|
|
11
12
|
constructor(serverless, options = {}) {
|
|
@@ -29,6 +30,11 @@ class DefinitionGenerator {
|
|
|
29
30
|
this.operationIds = []
|
|
30
31
|
this.schemaIDs = []
|
|
31
32
|
|
|
33
|
+
this.componentTypes = {
|
|
34
|
+
schemas: 'schemas',
|
|
35
|
+
securitySchemes: 'securitySchemes'
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
try {
|
|
33
39
|
this.refParserOptions = require(path.resolve('options', 'ref-parser.js'))
|
|
34
40
|
} catch (err) {
|
|
@@ -39,6 +45,15 @@ class DefinitionGenerator {
|
|
|
39
45
|
|
|
40
46
|
async parse() {
|
|
41
47
|
this.createInfo()
|
|
48
|
+
|
|
49
|
+
if (this.serverless.service.custom.documentation.securitySchemes) {
|
|
50
|
+
this.createSecuritySchemes(this.serverless.service.custom.documentation.securitySchemes)
|
|
51
|
+
|
|
52
|
+
if (this.serverless.service.custom.documentation.security) {
|
|
53
|
+
this.openAPI.security = this.serverless.service.custom.documentation.security
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
await this.createPaths()
|
|
43
58
|
.catch(err => {
|
|
44
59
|
throw err
|
|
@@ -265,6 +280,10 @@ class DefinitionGenerator {
|
|
|
265
280
|
obj.externalDocs = documentation.externalDocumentation
|
|
266
281
|
}
|
|
267
282
|
|
|
283
|
+
if (Object.keys(documentation).includes('security')) {
|
|
284
|
+
obj.security = documentation.security
|
|
285
|
+
}
|
|
286
|
+
|
|
268
287
|
if (Object.keys(documentation).includes('deprecated'))
|
|
269
288
|
obj.deprecated = documentation.deprecated
|
|
270
289
|
|
|
@@ -442,42 +461,13 @@ class DefinitionGenerator {
|
|
|
442
461
|
}
|
|
443
462
|
|
|
444
463
|
async dereferenceSchema(schema) {
|
|
445
|
-
|
|
464
|
+
let deReferencedSchema = await $RefParser.dereference(schema, this.refParserOptions)
|
|
446
465
|
.catch(err => {
|
|
447
466
|
console.error(err)
|
|
448
467
|
throw err
|
|
449
468
|
})
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
async schemaCreator(schema, name) {
|
|
453
|
-
const addToComponents = (schema, name) => {
|
|
454
|
-
const schemaObj = {
|
|
455
|
-
[name]: schema
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (this.openAPI?.components) {
|
|
459
|
-
if (this.openAPI.components?.schemas) {
|
|
460
|
-
Object.assign(this.openAPI.components.schemas, schemaObj)
|
|
461
|
-
} else {
|
|
462
|
-
Object.assign(this.openAPI.components, {schemas: schemaObj})
|
|
463
|
-
}
|
|
464
|
-
} else {
|
|
465
|
-
const components = {
|
|
466
|
-
components: {
|
|
467
|
-
schemas: schemaObj
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
Object.assign(this.openAPI, components)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
let deReferencedSchema = await this.dereferenceSchema(schema)
|
|
476
|
-
.catch((err) => {
|
|
477
|
-
throw err
|
|
478
|
-
})
|
|
479
469
|
|
|
480
|
-
// deal with schemas that have been de-referenced poorly
|
|
470
|
+
// deal with schemas that have been de-referenced poorly: naive
|
|
481
471
|
if (deReferencedSchema.$ref === '#') {
|
|
482
472
|
const oldRef = schema.$ref
|
|
483
473
|
const path = oldRef.split('/')
|
|
@@ -492,35 +482,184 @@ class DefinitionGenerator {
|
|
|
492
482
|
})
|
|
493
483
|
}
|
|
494
484
|
|
|
495
|
-
|
|
485
|
+
return deReferencedSchema
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async schemaCreator(schema, name) {
|
|
496
489
|
let schemaName = name
|
|
497
|
-
|
|
498
|
-
|
|
490
|
+
let finalName = schemaName
|
|
491
|
+
const dereferencedSchema = await this.dereferenceSchema(schema)
|
|
492
|
+
.catch(err => {
|
|
493
|
+
console.error(err)
|
|
494
|
+
throw err
|
|
495
|
+
})
|
|
499
496
|
|
|
500
|
-
|
|
497
|
+
const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, schemaName)
|
|
498
|
+
|
|
499
|
+
for (const convertedSchemaName of Object.keys(convertedSchemas.schemas)) {
|
|
500
|
+
const convertedSchema = convertedSchemas.schemas[convertedSchemaName]
|
|
501
|
+
if (this.existsInComponents(convertedSchemaName)) {
|
|
502
|
+
if (this.isTheSameSchema(convertedSchema, convertedSchemaName) === false) {
|
|
503
|
+
if (convertedSchemaName === schemaName) {
|
|
504
|
+
finalName = `${schemaName}-${uuid()}`
|
|
505
|
+
this.addToComponents(this.componentTypes.schemas, convertedSchema, finalName)
|
|
506
|
+
} else
|
|
507
|
+
this.addToComponents(this.componentTypes.schemas, convertedSchema, convertedSchemaName)
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
this.addToComponents(this.componentTypes.schemas, convertedSchema, convertedSchemaName)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
501
513
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
let ref = `#/components/schemas/`
|
|
514
|
+
return `#/components/schemas/${finalName}`
|
|
515
|
+
}
|
|
505
516
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
517
|
+
existsInComponents(name) {
|
|
518
|
+
return Boolean(this.openAPI?.components?.schemas?.[name])
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
isTheSameSchema(schema, otherSchemaName) {
|
|
522
|
+
return isEqual(schema, this.openAPI.components.schemas[otherSchemaName])
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
addToComponents(type, schema, name) {
|
|
526
|
+
const schemaObj = {
|
|
527
|
+
[name]: schema
|
|
528
|
+
}
|
|
511
529
|
|
|
512
|
-
|
|
513
|
-
|
|
530
|
+
if (this.openAPI?.components) {
|
|
531
|
+
if (this.openAPI.components[type]) {
|
|
532
|
+
Object.assign(this.openAPI.components[type], schemaObj)
|
|
514
533
|
} else {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
534
|
+
Object.assign(this.openAPI.components, {[type]: schemaObj})
|
|
535
|
+
}
|
|
536
|
+
} else {
|
|
537
|
+
const components = {
|
|
538
|
+
components: {
|
|
539
|
+
[type]: schemaObj
|
|
521
540
|
}
|
|
522
541
|
}
|
|
542
|
+
|
|
543
|
+
Object.assign(this.openAPI, components)
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
createSecuritySchemes(securitySchemes) {
|
|
548
|
+
for (const scheme of Object.keys(securitySchemes)) {
|
|
549
|
+
const securityScheme = securitySchemes[scheme]
|
|
550
|
+
const schema = {}
|
|
551
|
+
|
|
552
|
+
if (securityScheme.description)
|
|
553
|
+
schema.description = securityScheme.description
|
|
554
|
+
|
|
555
|
+
switch(securityScheme.type.toLowerCase()) {
|
|
556
|
+
case 'apikey':
|
|
557
|
+
const apiKeyScheme = this.createAPIKeyScheme(securityScheme)
|
|
558
|
+
schema.type = 'apiKey'
|
|
559
|
+
Object.assign(schema, apiKeyScheme)
|
|
560
|
+
break;
|
|
561
|
+
|
|
562
|
+
case 'http':
|
|
563
|
+
const HTTPScheme = this.createHTTPScheme(securityScheme)
|
|
564
|
+
schema.type = 'http'
|
|
565
|
+
Object.assign(schema, HTTPScheme)
|
|
566
|
+
break;
|
|
567
|
+
|
|
568
|
+
case 'openidconnect':
|
|
569
|
+
const openIdConnectScheme = this.createOpenIDConnectScheme(securityScheme)
|
|
570
|
+
schema.type = 'openIdConnect'
|
|
571
|
+
Object.assign(schema, openIdConnectScheme)
|
|
572
|
+
break;
|
|
573
|
+
|
|
574
|
+
case 'oauth2':
|
|
575
|
+
const oAuth2Scheme = this.createOAuth2Scheme(securityScheme)
|
|
576
|
+
schema.type = 'oauth2'
|
|
577
|
+
Object.assign(schema, oAuth2Scheme)
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
this.addToComponents(this.componentTypes.securitySchemes, schema, scheme)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
createAPIKeyScheme(securitySchema) {
|
|
586
|
+
const schema = {}
|
|
587
|
+
if (securitySchema.name)
|
|
588
|
+
schema.name = securitySchema.name
|
|
589
|
+
else
|
|
590
|
+
throw new Error('Security Scheme for "apiKey" requires the name of the header, query or cookie parameter to be used')
|
|
591
|
+
|
|
592
|
+
if (securitySchema.in)
|
|
593
|
+
schema.in = securitySchema.in
|
|
594
|
+
else
|
|
595
|
+
throw new Error('Security Scheme for "apiKey" requires the location of the API key: header, query or cookie parameter')
|
|
596
|
+
|
|
597
|
+
return schema
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
createHTTPScheme(securitySchema) {
|
|
601
|
+
const schema = {}
|
|
602
|
+
|
|
603
|
+
if (securitySchema.scheme)
|
|
604
|
+
schema.scheme = securitySchema.scheme
|
|
605
|
+
else
|
|
606
|
+
throw new Error('Security Scheme for "http" requires scheme')
|
|
607
|
+
|
|
608
|
+
if (securitySchema.bearerFormat)
|
|
609
|
+
schema.bearerFormat = securitySchema.bearerFormat
|
|
610
|
+
|
|
611
|
+
return schema
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
createOpenIDConnectScheme(securitySchema) {
|
|
615
|
+
const schema = {}
|
|
616
|
+
if (securitySchema.openIdConnectUrl)
|
|
617
|
+
schema.openIdConnectUrl = securitySchema.openIdConnectUrl
|
|
618
|
+
else
|
|
619
|
+
throw new Error('Security Scheme for "openIdConnect" requires openIdConnectUrl')
|
|
620
|
+
|
|
621
|
+
return schema
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
createOAuth2Scheme(securitySchema) {
|
|
625
|
+
const schema = {}
|
|
626
|
+
if (securitySchema.flows) {
|
|
627
|
+
const flows = this.createOAuthFlows(securitySchema.flows)
|
|
628
|
+
Object.assign(schema, {flows: flows})
|
|
629
|
+
} else
|
|
630
|
+
throw new Error('Security Scheme for "oauth2" requires flows')
|
|
631
|
+
|
|
632
|
+
return schema
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
createOAuthFlows(flows) {
|
|
636
|
+
const obj = {}
|
|
637
|
+
for (const flow of Object.keys(flows)) {
|
|
638
|
+
const schema = {}
|
|
639
|
+
if (["implicit", 'authorizationCode'].includes(flow))
|
|
640
|
+
if (flows[flow].authorizationUrl)
|
|
641
|
+
schema.authorizationUrl = flows[flow].authorizationUrl
|
|
642
|
+
else
|
|
643
|
+
throw new Error(`oAuth2 ${flow} flow requires an authorizationUrl`)
|
|
644
|
+
|
|
645
|
+
if (['password', 'clientCredentials', 'authorizationCode'].includes(flow)) {
|
|
646
|
+
if (flows[flow].tokenUrl)
|
|
647
|
+
schema.tokenUrl = flows[flow].tokenUrl
|
|
648
|
+
else
|
|
649
|
+
throw new Error(`oAuth2 ${flow} flow requires a tokenUrl`)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (flows[flow].refreshUrl)
|
|
653
|
+
schema.refreshUrl = flows[flow].refreshUrl
|
|
654
|
+
|
|
655
|
+
if (flows[flow].scopes)
|
|
656
|
+
schema.scopes = flows[flow].scopes
|
|
657
|
+
else
|
|
658
|
+
throw new Error(`oAuth2 ${flow} flow requires scopes`)
|
|
659
|
+
|
|
660
|
+
Object.assign(obj, {[flow]: schema})
|
|
523
661
|
}
|
|
662
|
+
return obj
|
|
524
663
|
}
|
|
525
664
|
|
|
526
665
|
createExamples(examples) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require('fs').promises
|
|
4
4
|
const path = require('path')
|
|
5
5
|
const sinon = require('sinon')
|
|
6
|
-
const $RefParser = require("@apidevtools/json-schema-ref-parser")
|
|
6
|
+
const $RefParser = require("@apidevtools/json-schema-ref-parser")
|
|
7
7
|
const expect = require('chai').expect
|
|
8
8
|
|
|
9
9
|
const serverlessMock = require('../helpers/serverless')
|
|
@@ -271,6 +271,387 @@ describe('DefinitionGenerator', () => {
|
|
|
271
271
|
});
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
+
describe('createSecuritySchemes', () => {
|
|
275
|
+
describe('API Keys', () => {
|
|
276
|
+
it('should add an API Key security scheme to components', function() {
|
|
277
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
278
|
+
'api_key': {
|
|
279
|
+
type: 'apiKey',
|
|
280
|
+
name: 'Authorization',
|
|
281
|
+
in: 'header'
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
286
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
287
|
+
|
|
288
|
+
expect(definitionGenerator.openAPI).to.be.an('object')
|
|
289
|
+
expect(definitionGenerator.openAPI.components).to.be.an('object')
|
|
290
|
+
expect(definitionGenerator.openAPI.components).to.have.property('securitySchemes')
|
|
291
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.be.an('object')
|
|
292
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('api_key')
|
|
293
|
+
expect(definitionGenerator.openAPI.components.securitySchemes.api_key).to.have.property('type')
|
|
294
|
+
expect(definitionGenerator.openAPI.components.securitySchemes.api_key.type).to.be.equal('apiKey')
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should throw an error when name is missing from an API Key scheme', function() {
|
|
298
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
299
|
+
'api_key': {
|
|
300
|
+
type: 'apiKey',
|
|
301
|
+
in: 'header'
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
306
|
+
expect(() => {
|
|
307
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
308
|
+
}).to.throw('Security Scheme for "apiKey" requires the name of the header, query or cookie parameter to be used')
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should throw an error when in is missing from an API Key scheme', function() {
|
|
312
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
313
|
+
'api_key': {
|
|
314
|
+
type: 'apiKey',
|
|
315
|
+
name: 'Authorization',
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
320
|
+
expect(() => {
|
|
321
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
322
|
+
}).to.throw('Security Scheme for "apiKey" requires the location of the API key: header, query or cookie parameter')
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('HTTP', () => {
|
|
327
|
+
it('should add an HTTP security scheme to components', function() {
|
|
328
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
329
|
+
'http_key': {
|
|
330
|
+
type: 'http',
|
|
331
|
+
scheme: 'basic'
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
336
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
337
|
+
|
|
338
|
+
expect(definitionGenerator.openAPI).to.be.an('object')
|
|
339
|
+
expect(definitionGenerator.openAPI.components).to.be.an('object')
|
|
340
|
+
expect(definitionGenerator.openAPI.components).to.have.property('securitySchemes')
|
|
341
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.be.an('object')
|
|
342
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('http_key')
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should throw an error when scheme is missing from an HTTP scheme', function() {
|
|
346
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
347
|
+
'http_key': {
|
|
348
|
+
type: 'http',
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
353
|
+
expect(() => {
|
|
354
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
355
|
+
}).to.throw('Security Scheme for "http" requires scheme')
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe('openIdConnect', () => {
|
|
360
|
+
it('should add an openIdConnect security scheme to components', function() {
|
|
361
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
362
|
+
'openIdConnect_key': {
|
|
363
|
+
type: 'openIdConnect',
|
|
364
|
+
openIdConnectUrl: 'http://example.com'
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
369
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
370
|
+
|
|
371
|
+
expect(definitionGenerator.openAPI).to.be.an('object')
|
|
372
|
+
expect(definitionGenerator.openAPI.components).to.be.an('object')
|
|
373
|
+
expect(definitionGenerator.openAPI.components).to.have.property('securitySchemes')
|
|
374
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.be.an('object')
|
|
375
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('openIdConnect_key')
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should throw an error when openIdConnectUrl is missing from an openIdConnect scheme', function() {
|
|
379
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
380
|
+
'openIdConnect_key': {
|
|
381
|
+
type: 'openIdConnect',
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
386
|
+
expect(() => {
|
|
387
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
388
|
+
}).to.throw('Security Scheme for "openIdConnect" requires openIdConnectUrl')
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe('oauth2', () => {
|
|
393
|
+
it('should add an oauth2 security scheme to components', function() {
|
|
394
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
395
|
+
'oAuth2_key': {
|
|
396
|
+
type: 'oauth2',
|
|
397
|
+
flows: {
|
|
398
|
+
implicit: {
|
|
399
|
+
authorizationUrl: 'http://example.org/api/oauth/dialog',
|
|
400
|
+
scopes: {
|
|
401
|
+
'write:pets': 'modify pets in your account',
|
|
402
|
+
'read:pets': 'read your pets'
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
410
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
411
|
+
|
|
412
|
+
expect(definitionGenerator.openAPI).to.be.an('object')
|
|
413
|
+
expect(definitionGenerator.openAPI.components).to.be.an('object')
|
|
414
|
+
expect(definitionGenerator.openAPI.components).to.have.property('securitySchemes')
|
|
415
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.be.an('object')
|
|
416
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('oAuth2_key')
|
|
417
|
+
expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key).to.be.an('object')
|
|
418
|
+
expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key).to.have.property('type')
|
|
419
|
+
expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key).to.have.property('flows')
|
|
420
|
+
expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key.flows).to.be.an('object')
|
|
421
|
+
expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key.flows).to.have.property('implicit')
|
|
422
|
+
expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key.flows.implicit).to.be.an('object')
|
|
423
|
+
expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key.flows.implicit).to.have.property('scopes')
|
|
424
|
+
expect(definitionGenerator.openAPI.components.securitySchemes.oAuth2_key.flows.implicit.scopes).to.be.an('object')
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('should throw an error when flows is missing from an oauth2 scheme', function() {
|
|
428
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
429
|
+
'oAuth2_key': {
|
|
430
|
+
type: 'oauth2',
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
435
|
+
expect(() => {
|
|
436
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
437
|
+
}).to.throw('Security Scheme for "oauth2" requires flows')
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should throw an error when authorizationUrl is missing from an oauth2 implicit flow scheme', function() {
|
|
441
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
442
|
+
'oAuth2_key': {
|
|
443
|
+
type: 'oauth2',
|
|
444
|
+
flows: {
|
|
445
|
+
implicit: {
|
|
446
|
+
scopes: {
|
|
447
|
+
'write:pets': 'modify pets in your account',
|
|
448
|
+
'read:pets': 'read your pets'
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
456
|
+
expect(() => {
|
|
457
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
458
|
+
}).to.throw('oAuth2 implicit flow requires an authorizationUrl')
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should throw an error when authorizationUrl is missing from an oauth2 authorizationCode flow scheme', function() {
|
|
462
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
463
|
+
'oAuth2_key': {
|
|
464
|
+
type: 'oauth2',
|
|
465
|
+
flows: {
|
|
466
|
+
authorizationCode: {
|
|
467
|
+
tokenUrl: 'http://example.com',
|
|
468
|
+
scopes: {
|
|
469
|
+
'write:pets': 'modify pets in your account',
|
|
470
|
+
'read:pets': 'read your pets'
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
478
|
+
expect(() => {
|
|
479
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
480
|
+
}).to.throw('oAuth2 authorizationCode flow requires an authorizationUrl')
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should throw an error when tokenUrl is missing from an oauth2 authorizationCode flow scheme', function() {
|
|
484
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
485
|
+
'oAuth2_key': {
|
|
486
|
+
type: 'oauth2',
|
|
487
|
+
flows: {
|
|
488
|
+
authorizationCode: {
|
|
489
|
+
authorizationUrl: 'http://example.org/api/oauth/dialog',
|
|
490
|
+
scopes: {
|
|
491
|
+
'write:pets': 'modify pets in your account',
|
|
492
|
+
'read:pets': 'read your pets'
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
500
|
+
expect(() => {
|
|
501
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
502
|
+
}).to.throw('oAuth2 authorizationCode flow requires a tokenUrl')
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('should throw an error when tokenUrl is missing from an oauth2 password flow scheme', function() {
|
|
506
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
507
|
+
'oAuth2_key': {
|
|
508
|
+
type: 'oauth2',
|
|
509
|
+
flows: {
|
|
510
|
+
password: {
|
|
511
|
+
scopes: {
|
|
512
|
+
'write:pets': 'modify pets in your account',
|
|
513
|
+
'read:pets': 'read your pets'
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
521
|
+
expect(() => {
|
|
522
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
523
|
+
}).to.throw('oAuth2 password flow requires a tokenUrl')
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('should throw an error when tokenUrl is missing from an oauth2 clientCredentials flow scheme', function() {
|
|
527
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
528
|
+
'oAuth2_key': {
|
|
529
|
+
type: 'oauth2',
|
|
530
|
+
flows: {
|
|
531
|
+
clientCredentials: {
|
|
532
|
+
scopes: {
|
|
533
|
+
'write:pets': 'modify pets in your account',
|
|
534
|
+
'read:pets': 'read your pets'
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
542
|
+
expect(() => {
|
|
543
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
544
|
+
}).to.throw('oAuth2 clientCredentials flow requires a tokenUrl')
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('should throw an error when scopes is missing from an oauth2 clientCredentials flow scheme', function() {
|
|
548
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
549
|
+
'oAuth2_key': {
|
|
550
|
+
type: 'oauth2',
|
|
551
|
+
flows: {
|
|
552
|
+
clientCredentials: {
|
|
553
|
+
tokenUrl: 'http://example.com',
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
560
|
+
expect(() => {
|
|
561
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
562
|
+
}).to.throw('oAuth2 clientCredentials flow requires scopes')
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it('should throw an error when scopes is missing from an oauth2 authorizationCode flow scheme', function() {
|
|
566
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
567
|
+
'oAuth2_key': {
|
|
568
|
+
type: 'oauth2',
|
|
569
|
+
flows: {
|
|
570
|
+
authorizationCode: {
|
|
571
|
+
tokenUrl: 'http://example.com',
|
|
572
|
+
authorizationUrl: 'http://example.org/api/oauth/dialog',
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
579
|
+
expect(() => {
|
|
580
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
581
|
+
}).to.throw('oAuth2 authorizationCode flow requires scopes')
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should throw an error when scopes is missing from an oauth2 password flow scheme', function() {
|
|
585
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
586
|
+
'oAuth2_key': {
|
|
587
|
+
type: 'oauth2',
|
|
588
|
+
flows: {
|
|
589
|
+
password: {
|
|
590
|
+
tokenUrl: 'http://example.com',
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
597
|
+
expect(() => {
|
|
598
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
599
|
+
}).to.throw('oAuth2 password flow requires scopes')
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
it('should throw an error when scopes is missing from an oauth2 implicit flow scheme', function() {
|
|
603
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
604
|
+
'oAuth2_key': {
|
|
605
|
+
type: 'oauth2',
|
|
606
|
+
flows: {
|
|
607
|
+
implicit: {
|
|
608
|
+
authorizationUrl: 'http://example.org/api/oauth/dialog',
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
615
|
+
expect(() => {
|
|
616
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
617
|
+
}).to.throw('oAuth2 implicit flow requires scopes')
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
describe('Multiple Schemes', () => {
|
|
622
|
+
it('should add an oauth2 and an apiKey security scheme to components', function() {
|
|
623
|
+
mockServerless.service.custom.documentation.securitySchemes = {
|
|
624
|
+
'oAuth2_key': {
|
|
625
|
+
type: 'oauth2',
|
|
626
|
+
flows: {
|
|
627
|
+
implicit: {
|
|
628
|
+
authorizationUrl: 'http://example.org/api/oauth/dialog',
|
|
629
|
+
scopes: {
|
|
630
|
+
'write:pets': 'modify pets in your account',
|
|
631
|
+
'read:pets': 'read your pets'
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
'api_key': {
|
|
637
|
+
type: 'apiKey',
|
|
638
|
+
name: 'Authorization',
|
|
639
|
+
in: 'header'
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
644
|
+
definitionGenerator.createSecuritySchemes(mockServerless.service.custom.documentation.securitySchemes)
|
|
645
|
+
expect(definitionGenerator.openAPI).to.be.an('object')
|
|
646
|
+
expect(definitionGenerator.openAPI.components).to.be.an('object')
|
|
647
|
+
expect(definitionGenerator.openAPI.components).to.have.property('securitySchemes')
|
|
648
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.be.an('object')
|
|
649
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('oAuth2_key')
|
|
650
|
+
expect(definitionGenerator.openAPI.components.securitySchemes).to.have.property('api_key')
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
|
|
274
655
|
describe('createTags', () => {
|
|
275
656
|
it('should add tags to the openAPI object correctly', function() {
|
|
276
657
|
mockServerless.service.custom.documentation.tags = [{name: 'tag1'}]
|
|
@@ -328,234 +709,165 @@ describe('DefinitionGenerator', () => {
|
|
|
328
709
|
expect(expected).to.equal('#/components/schemas/main')
|
|
329
710
|
});
|
|
330
711
|
|
|
331
|
-
it('should
|
|
332
|
-
const complexSchema = {
|
|
333
|
-
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
334
|
-
"title": "JSON API Schema",
|
|
335
|
-
"description": "This is a blah blah for responses in the JSON API format. For more, see http://jsonapi.org",
|
|
336
|
-
"type": "object",
|
|
337
|
-
"required": [
|
|
338
|
-
"errors"
|
|
339
|
-
],
|
|
340
|
-
"properties": {
|
|
341
|
-
"errors": {
|
|
342
|
-
"type": "array",
|
|
343
|
-
"items": {
|
|
344
|
-
"$ref": "#/definitions/error"
|
|
345
|
-
},
|
|
346
|
-
"uniqueItems": true
|
|
347
|
-
}
|
|
348
|
-
},
|
|
349
|
-
"definitions": {
|
|
350
|
-
"error": {
|
|
351
|
-
"type": "object",
|
|
352
|
-
"properties": {
|
|
353
|
-
"id": {
|
|
354
|
-
"description": "A unique identifier for this particular occurrence of the problem.",
|
|
355
|
-
"type": "string"
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
712
|
+
it('should not overwrite a schema that has the same name and same schema as a pre-existing schema', async function() {
|
|
361
713
|
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
362
|
-
const expected = await definitionGenerator.schemaCreator(complexSchema, 'PutRequest')
|
|
363
|
-
.catch((err) => {
|
|
364
|
-
console.error(err)
|
|
365
|
-
})
|
|
366
714
|
|
|
367
|
-
|
|
368
|
-
expect(definitionGenerator.openAPI.components.schemas).to.not.have.property('error')
|
|
369
|
-
expect(expected).to.equal('#/components/schemas/PutRequest')
|
|
370
|
-
});
|
|
715
|
+
const spy = sinon.spy(definitionGenerator, 'addToComponents')
|
|
371
716
|
|
|
372
|
-
it(`should not overwrite an object that already exists in the components if they're the same`, async function() {
|
|
373
717
|
const complexSchema = {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
"required": [
|
|
379
|
-
"errors"
|
|
380
|
-
],
|
|
381
|
-
"properties": {
|
|
382
|
-
"errors": {
|
|
383
|
-
"type": "array",
|
|
384
|
-
"items": {
|
|
385
|
-
"$ref": "#/definitions/error"
|
|
386
|
-
},
|
|
387
|
-
"uniqueItems": true
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
"definitions": {
|
|
391
|
-
"error": {
|
|
392
|
-
"type": "object",
|
|
393
|
-
"properties": {
|
|
394
|
-
"id": {
|
|
395
|
-
"description": "A unique identifier for this particular occurrence of the problem.",
|
|
396
|
-
"type": "string"
|
|
397
|
-
}
|
|
398
|
-
}
|
|
718
|
+
type: 'object',
|
|
719
|
+
properties: {
|
|
720
|
+
error: {
|
|
721
|
+
type: 'string'
|
|
399
722
|
}
|
|
400
723
|
}
|
|
401
724
|
}
|
|
402
|
-
|
|
403
|
-
let expected = await definitionGenerator.schemaCreator(complexSchema, '
|
|
725
|
+
|
|
726
|
+
let expected = await definitionGenerator.schemaCreator(complexSchema, 'main')
|
|
404
727
|
.catch((err) => {
|
|
405
728
|
console.error(err)
|
|
406
729
|
})
|
|
407
730
|
|
|
408
|
-
expect(definitionGenerator.openAPI.components.schemas).to.have.property('
|
|
409
|
-
expect(definitionGenerator.openAPI.components.schemas).to.
|
|
410
|
-
expect(expected).to.equal('#/components/schemas/
|
|
731
|
+
expect(definitionGenerator.openAPI.components.schemas).to.have.property('main')
|
|
732
|
+
expect(JSON.stringify(definitionGenerator.openAPI.components.schemas.main)).to.equal(JSON.stringify(complexSchema))
|
|
733
|
+
expect(expected).to.equal('#/components/schemas/main')
|
|
411
734
|
|
|
412
|
-
expected = await definitionGenerator.schemaCreator(complexSchema, '
|
|
413
|
-
|
|
735
|
+
expected = await definitionGenerator.schemaCreator(complexSchema, 'main')
|
|
736
|
+
.catch((err) => {
|
|
414
737
|
console.error(err)
|
|
415
738
|
})
|
|
416
739
|
|
|
417
|
-
expect(
|
|
418
|
-
expect(
|
|
419
|
-
|
|
740
|
+
expect(expected).to.equal('#/components/schemas/main')
|
|
741
|
+
expect(spy.callCount).to.be.equal(1)
|
|
742
|
+
|
|
743
|
+
spy.resetHistory()
|
|
420
744
|
});
|
|
421
745
|
|
|
422
|
-
it(
|
|
746
|
+
it('should not overwrite a schema that has the same name and same schema as a pre-existing schema but in a slightly different order', async function() {
|
|
747
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
748
|
+
|
|
749
|
+
const spy = sinon.spy(definitionGenerator, 'addToComponents')
|
|
750
|
+
|
|
423
751
|
const complexSchema = {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
"errors"
|
|
430
|
-
],
|
|
431
|
-
"properties": {
|
|
432
|
-
"errors": {
|
|
433
|
-
"type": "array",
|
|
434
|
-
"items": {
|
|
435
|
-
"$ref": "#/definitions/error"
|
|
436
|
-
},
|
|
437
|
-
"uniqueItems": true
|
|
438
|
-
}
|
|
439
|
-
},
|
|
440
|
-
"definitions": {
|
|
441
|
-
"error": {
|
|
442
|
-
"type": "object",
|
|
443
|
-
"properties": {
|
|
444
|
-
"id": {
|
|
445
|
-
"description": "A unique identifier for this particular occurrence of the problem.",
|
|
446
|
-
"type": "string"
|
|
447
|
-
}
|
|
448
|
-
}
|
|
752
|
+
type: 'object',
|
|
753
|
+
properties: {
|
|
754
|
+
error: {
|
|
755
|
+
type: 'string',
|
|
756
|
+
format: 'uuid'
|
|
449
757
|
}
|
|
450
758
|
}
|
|
451
759
|
}
|
|
452
|
-
|
|
453
|
-
let expected = await definitionGenerator.schemaCreator(complexSchema, '
|
|
760
|
+
|
|
761
|
+
let expected = await definitionGenerator.schemaCreator(complexSchema, 'main')
|
|
454
762
|
.catch((err) => {
|
|
455
763
|
console.error(err)
|
|
456
764
|
})
|
|
457
765
|
|
|
458
|
-
expect(definitionGenerator.openAPI.components.schemas).to.have.property('
|
|
459
|
-
expect(definitionGenerator.openAPI.components.schemas).to.
|
|
460
|
-
expect(expected).to.equal('#/components/schemas/
|
|
766
|
+
expect(definitionGenerator.openAPI.components.schemas).to.have.property('main')
|
|
767
|
+
expect(JSON.stringify(definitionGenerator.openAPI.components.schemas.main)).to.equal(JSON.stringify(complexSchema))
|
|
768
|
+
expect(expected).to.equal('#/components/schemas/main')
|
|
461
769
|
|
|
462
|
-
|
|
463
|
-
type: '
|
|
770
|
+
const complexSchema2 = {
|
|
771
|
+
type: 'object',
|
|
772
|
+
properties: {
|
|
773
|
+
error: {
|
|
774
|
+
format: 'uuid',
|
|
775
|
+
type: 'string'
|
|
776
|
+
}
|
|
777
|
+
}
|
|
464
778
|
}
|
|
465
779
|
|
|
466
|
-
expected = await definitionGenerator.schemaCreator(
|
|
467
|
-
|
|
780
|
+
expected = await definitionGenerator.schemaCreator(complexSchema2, 'main')
|
|
781
|
+
.catch((err) => {
|
|
468
782
|
console.error(err)
|
|
469
783
|
})
|
|
470
784
|
|
|
471
|
-
expect(
|
|
472
|
-
expect(
|
|
785
|
+
expect(expected).to.equal('#/components/schemas/main')
|
|
786
|
+
expect(spy.callCount).to.be.equal(1)
|
|
473
787
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
expect(definitionGenerator.openAPI.components.schemas).to.have.property(newSchemaStr[newSchemaStr.length-1])
|
|
788
|
+
spy.resetHistory()
|
|
789
|
+
});
|
|
477
790
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
// console.error(err)
|
|
481
|
-
// })
|
|
791
|
+
it('should add a schema to components when a name already exists but the schema is different', async function() {
|
|
792
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
482
793
|
|
|
483
|
-
|
|
484
|
-
// expect(definitionGenerator.openAPI.components.schemas).to.have.property('error')
|
|
485
|
-
// console.log(expected)
|
|
486
|
-
// expect(expected)
|
|
487
|
-
// expect(expected).to.equal('#/components/schemas/PutRequest1')
|
|
794
|
+
const spy = sinon.spy(definitionGenerator, 'addToComponents')
|
|
488
795
|
|
|
489
|
-
complexSchema
|
|
490
|
-
type: '
|
|
796
|
+
const complexSchema = {
|
|
797
|
+
type: 'object',
|
|
798
|
+
properties: {
|
|
799
|
+
error: {
|
|
800
|
+
type: 'string'
|
|
801
|
+
}
|
|
802
|
+
}
|
|
491
803
|
}
|
|
492
804
|
|
|
493
|
-
expected = await definitionGenerator.schemaCreator(complexSchema, '
|
|
805
|
+
let expected = await definitionGenerator.schemaCreator(complexSchema, 'main')
|
|
494
806
|
.catch((err) => {
|
|
495
807
|
console.error(err)
|
|
496
808
|
})
|
|
497
809
|
|
|
498
|
-
expect(definitionGenerator.openAPI.components.schemas).to.have.property('
|
|
499
|
-
expect(definitionGenerator.openAPI.components.schemas).to.
|
|
500
|
-
expect(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
810
|
+
expect(definitionGenerator.openAPI.components.schemas).to.have.property('main')
|
|
811
|
+
expect(JSON.stringify(definitionGenerator.openAPI.components.schemas.main)).to.equal(JSON.stringify(complexSchema))
|
|
812
|
+
expect(expected).to.equal('#/components/schemas/main')
|
|
813
|
+
|
|
814
|
+
const complexSchema2 = JSON.parse(JSON.stringify(complexSchema))
|
|
815
|
+
complexSchema2.properties.error.format = 'uuid'
|
|
816
|
+
|
|
817
|
+
expected = await definitionGenerator.schemaCreator(complexSchema2, 'main')
|
|
818
|
+
.catch((err) => {
|
|
819
|
+
console.error(err)
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
const splitPath = expected.split('/')
|
|
823
|
+
expect(v4.test(splitPath[3].split('main-')[1])).to.be.true
|
|
824
|
+
expect(definitionGenerator.openAPI.components.schemas[splitPath[3]]).to.be.an('object')
|
|
825
|
+
expect(definitionGenerator.openAPI.components.schemas[splitPath[3]].properties.error).to.have.property('format')
|
|
826
|
+
expect(spy.callCount).to.be.equal(2)
|
|
827
|
+
|
|
828
|
+
spy.resetHistory()
|
|
504
829
|
});
|
|
505
830
|
|
|
506
|
-
it(
|
|
831
|
+
it('should correctly dereference a schema with definitions and add to the components', async function() {
|
|
832
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
833
|
+
|
|
507
834
|
const complexSchema = {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
"required": [
|
|
513
|
-
"errors"
|
|
514
|
-
],
|
|
515
|
-
"properties": {
|
|
516
|
-
"errors": {
|
|
517
|
-
"type": "array",
|
|
518
|
-
"items": {
|
|
519
|
-
"$ref": "#/definitions/error"
|
|
520
|
-
},
|
|
521
|
-
"uniqueItems": true
|
|
835
|
+
type: 'object',
|
|
836
|
+
properties: {
|
|
837
|
+
error: {
|
|
838
|
+
'$ref': '#/definitions/error'
|
|
522
839
|
}
|
|
523
840
|
},
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
"properties": {
|
|
528
|
-
"id": {
|
|
529
|
-
"description": "A unique identifier for this particular occurrence of the problem.",
|
|
530
|
-
"type": "string"
|
|
531
|
-
}
|
|
532
|
-
}
|
|
841
|
+
definitions: {
|
|
842
|
+
error: {
|
|
843
|
+
type: "string"
|
|
533
844
|
}
|
|
534
845
|
}
|
|
535
846
|
}
|
|
536
|
-
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
537
|
-
let expected = await definitionGenerator.schemaCreator(complexSchema, 'PutRequest')
|
|
538
|
-
.catch((err) => {
|
|
539
|
-
console.error(err)
|
|
540
|
-
})
|
|
541
847
|
|
|
542
|
-
|
|
543
|
-
expect(definitionGenerator.openAPI.components.schemas).to.not.have.property('error')
|
|
544
|
-
expect(expected).to.equal('#/components/schemas/PutRequest')
|
|
848
|
+
const spy = sinon.spy(definitionGenerator, 'dereferenceSchema')
|
|
545
849
|
|
|
546
|
-
expected = await definitionGenerator.schemaCreator(complexSchema, '
|
|
850
|
+
const expected = await definitionGenerator.schemaCreator(complexSchema, 'main')
|
|
547
851
|
.catch((err) => {
|
|
548
852
|
console.error(err)
|
|
549
853
|
})
|
|
550
854
|
|
|
551
|
-
expect(definitionGenerator.openAPI.components.schemas).to.have.property('
|
|
552
|
-
expect(definitionGenerator.openAPI.components.schemas).to.
|
|
553
|
-
expect(definitionGenerator.openAPI.components.schemas).to.have.property('
|
|
554
|
-
expect(
|
|
855
|
+
expect(definitionGenerator.openAPI.components.schemas).to.have.property('main')
|
|
856
|
+
expect(definitionGenerator.openAPI.components.schemas.main.properties).to.have.property('error')
|
|
857
|
+
expect(definitionGenerator.openAPI.components.schemas.main.properties.error).to.have.property('type')
|
|
858
|
+
expect(definitionGenerator.openAPI.components.schemas.main.properties.error.type).to.be.equal('string')
|
|
859
|
+
expect(definitionGenerator.openAPI.components.schemas.main).to.not.have.property('definitions')
|
|
860
|
+
expect(expected).to.equal('#/components/schemas/main')
|
|
861
|
+
|
|
862
|
+
expect(spy.callCount).to.be.equal(1)
|
|
863
|
+
|
|
864
|
+
spy.resetHistory()
|
|
555
865
|
});
|
|
556
866
|
|
|
557
|
-
it('should handle a schema that has been
|
|
558
|
-
const
|
|
867
|
+
it('should handle a schema that has been incorrectly dereferenced', async function() {
|
|
868
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
869
|
+
|
|
870
|
+
const complexSchema = {
|
|
559
871
|
$schema: 'http://json-schema.org/draft-04/schema#',
|
|
560
872
|
title: 'JSON API Schema',
|
|
561
873
|
$ref: '#/definitions/Error',
|
|
@@ -565,22 +877,72 @@ describe('DefinitionGenerator', () => {
|
|
|
565
877
|
}
|
|
566
878
|
}
|
|
567
879
|
}
|
|
568
|
-
|
|
569
|
-
const
|
|
880
|
+
|
|
881
|
+
const spy = sinon.spy(definitionGenerator, 'dereferenceSchema')
|
|
882
|
+
|
|
883
|
+
const expected = await definitionGenerator.schemaCreator(complexSchema, 'main')
|
|
570
884
|
.catch((err) => {
|
|
571
885
|
console.error(err)
|
|
572
886
|
})
|
|
573
887
|
|
|
574
|
-
expect(definitionGenerator.openAPI.components.schemas).to.have.property('
|
|
575
|
-
expect(definitionGenerator.openAPI.components.schemas.
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
888
|
+
expect(definitionGenerator.openAPI.components.schemas).to.have.property('main')
|
|
889
|
+
expect(definitionGenerator.openAPI.components.schemas.main.properties).to.have.property('Error')
|
|
890
|
+
expect(definitionGenerator.openAPI.components.schemas.main.properties.Error).to.have.property('type')
|
|
891
|
+
expect(definitionGenerator.openAPI.components.schemas.main.properties.Error.type).to.be.equal('string')
|
|
892
|
+
expect(definitionGenerator.openAPI.components.schemas.main).to.not.have.property('$schema')
|
|
893
|
+
expect(definitionGenerator.openAPI.components.schemas.main).to.not.have.property('$definitions')
|
|
894
|
+
expect(expected).to.equal('#/components/schemas/main')
|
|
895
|
+
|
|
896
|
+
expect(spy.callCount).to.be.equal(2)
|
|
897
|
+
|
|
898
|
+
spy.resetHistory()
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
it('should add all schemas from a conversion to the components', async function() {
|
|
902
|
+
const complexSchema = {
|
|
903
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
904
|
+
"title": "JSON API Schema",
|
|
905
|
+
"description": "This is a blah blah for responses in the JSON API format. For more, see http://jsonapi.org",
|
|
906
|
+
"type": "object",
|
|
907
|
+
"properties": {
|
|
908
|
+
"street_address": {
|
|
909
|
+
"type": "string"
|
|
910
|
+
},
|
|
911
|
+
"country": {
|
|
912
|
+
"type": "string",
|
|
913
|
+
"default": "United States of America",
|
|
914
|
+
"enum": [
|
|
915
|
+
"United States of America",
|
|
916
|
+
"Canada"
|
|
917
|
+
]
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
"if": {
|
|
921
|
+
"properties": {
|
|
922
|
+
"country": {
|
|
923
|
+
"const": "United States of America"
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
"then": {
|
|
928
|
+
"properties": {
|
|
929
|
+
"postal_code": {
|
|
930
|
+
"pattern": "[0-9]{5}(-[0-9]{4})?"
|
|
931
|
+
}
|
|
580
932
|
}
|
|
581
933
|
}
|
|
582
|
-
}
|
|
583
|
-
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
const definitionGenerator = new DefinitionGenerator(mockServerless)
|
|
937
|
+
let expected = await definitionGenerator.schemaCreator(complexSchema, 'main')
|
|
938
|
+
.catch((err) => {
|
|
939
|
+
console.error(err)
|
|
940
|
+
})
|
|
941
|
+
|
|
942
|
+
expect(expected).to.equal('#/components/schemas/main')
|
|
943
|
+
expect(definitionGenerator.openAPI.components.schemas).to.have.property('main')
|
|
944
|
+
expect(Object.keys(definitionGenerator.openAPI.components.schemas).length).to.be.equal(2)
|
|
945
|
+
|
|
584
946
|
});
|
|
585
947
|
});
|
|
586
948
|
|