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 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
- type: object
308
- properties:
309
- message:
310
- type: string
311
- code:
312
- type: integer
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
- type: array
320
- items: *ErrorItem
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.32",
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.1.0",
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
- return await $RefParser.dereference(schema, this.refParserOptions)
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
- const convertedSchema = SchemaConvertor.convert(deReferencedSchema, name)
485
+ return deReferencedSchema
486
+ }
487
+
488
+ async schemaCreator(schema, name) {
496
489
  let schemaName = name
497
- if (this.schemaIDs.includes(schemaName))
498
- schemaName = `${name}-${uuid()}`
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
- this.schemaIDs.push(schemaName)
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
- for (const key of Object.keys(convertedSchema.schemas)) {
503
- if (key === name || key.split('-')[0] === name) {
504
- let ref = `#/components/schemas/`
514
+ return `#/components/schemas/${finalName}`
515
+ }
505
516
 
506
- if (this.openAPI?.components?.schemas?.[name]) {
507
- if (JSON.stringify(convertedSchema.schemas[key]) === JSON.stringify(this.openAPI.components.schemas[name])) {
508
- return `${ref}${name}`
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
- addToComponents(convertedSchema.schemas[key], schemaName)
513
- return `${ref}${schemaName}`
530
+ if (this.openAPI?.components) {
531
+ if (this.openAPI.components[type]) {
532
+ Object.assign(this.openAPI.components[type], schemaObj)
514
533
  } else {
515
- if (this.openAPI?.components?.schemas?.[key]) {
516
- if (JSON.stringify(convertedSchema.schemas[key]) !== JSON.stringify(this.openAPI.components.schemas[key])) {
517
- addToComponents(convertedSchema.schemas[key], key)
518
- }
519
- } else {
520
- addToComponents(convertedSchema.schemas[key], key)
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 add each definition of a complex schema to the components object', async function() {
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
- expect(definitionGenerator.openAPI.components.schemas).to.have.property('PutRequest')
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
- "$schema": "http://json-schema.org/draft-04/schema#",
375
- "title": "JSON API Schema",
376
- "description": "This is a blah blah for responses in the JSON API format. For more, see http://jsonapi.org",
377
- "type": "object",
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
- const definitionGenerator = new DefinitionGenerator(mockServerless)
403
- let expected = await definitionGenerator.schemaCreator(complexSchema, 'PutRequest')
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('PutRequest')
409
- expect(definitionGenerator.openAPI.components.schemas).to.not.have.property('error')
410
- expect(expected).to.equal('#/components/schemas/PutRequest')
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, 'PutRequest')
413
- .catch((err) => {
735
+ expected = await definitionGenerator.schemaCreator(complexSchema, 'main')
736
+ .catch((err) => {
414
737
  console.error(err)
415
738
  })
416
739
 
417
- expect(definitionGenerator.openAPI.components.schemas).to.have.property('PutRequest')
418
- expect(definitionGenerator.openAPI.components.schemas).to.not.have.property('error')
419
- expect(expected).to.equal('#/components/schemas/PutRequest')
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(`should create a new object for a similarly named object but with different properties`, async function() {
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
- "$schema": "http://json-schema.org/draft-04/schema#",
425
- "title": "JSON API Schema",
426
- "description": "This is a blah blah for responses in the JSON API format. For more, see http://jsonapi.org",
427
- "type": "object",
428
- "required": [
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
- const definitionGenerator = new DefinitionGenerator(mockServerless)
453
- let expected = await definitionGenerator.schemaCreator(complexSchema, 'PutRequest')
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('PutRequest')
459
- expect(definitionGenerator.openAPI.components.schemas).to.not.have.property('error')
460
- expect(expected).to.equal('#/components/schemas/PutRequest')
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
- complexSchema.properties.cheese = {
463
- type: 'string'
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(complexSchema, 'PutRequest')
467
- .catch((err) => {
780
+ expected = await definitionGenerator.schemaCreator(complexSchema2, 'main')
781
+ .catch((err) => {
468
782
  console.error(err)
469
783
  })
470
784
 
471
- expect(definitionGenerator.openAPI.components.schemas).to.have.property('PutRequest')
472
- expect(definitionGenerator.openAPI.components.schemas).to.not.have.property('error')
785
+ expect(expected).to.equal('#/components/schemas/main')
786
+ expect(spy.callCount).to.be.equal(1)
473
787
 
474
- let newSchemaStr = expected.split('/')
475
- expect(v4.test(newSchemaStr[newSchemaStr.length-1].split('PutRequest-')[1])).to.be.true
476
- expect(definitionGenerator.openAPI.components.schemas).to.have.property(newSchemaStr[newSchemaStr.length-1])
788
+ spy.resetHistory()
789
+ });
477
790
 
478
- // expected = await definitionGenerator.schemaCreator(complexSchema, 'PutRequest')
479
- // .catch((err) => {
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
- // expect(definitionGenerator.openAPI.components.schemas).to.have.property('PutRequest')
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.properties.wine = {
490
- type: 'string'
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, 'PutRequest')
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('PutRequest')
499
- expect(definitionGenerator.openAPI.components.schemas).to.not.have.property('error')
500
- expect(definitionGenerator.openAPI.components.schemas).to.have.property(newSchemaStr[newSchemaStr.length-1])
501
- newSchemaStr = expected.split('/')
502
- expect(v4.test(newSchemaStr[newSchemaStr.length-1].split('PutRequest-')[1])).to.be.true
503
- expect(definitionGenerator.openAPI.components.schemas).to.have.property(newSchemaStr[newSchemaStr.length-1])
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(`should create a new object for a differently named object but with same properties`, async function() {
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
- "$schema": "http://json-schema.org/draft-04/schema#",
509
- "title": "JSON API Schema",
510
- "description": "This is a blah blah for responses in the JSON API format. For more, see http://jsonapi.org",
511
- "type": "object",
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
- "definitions": {
525
- "error": {
526
- "type": "object",
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
- expect(definitionGenerator.openAPI.components.schemas).to.have.property('PutRequest')
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, 'ContactPutRequest')
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('PutRequest')
552
- expect(definitionGenerator.openAPI.components.schemas).to.not.have.property('error')
553
- expect(definitionGenerator.openAPI.components.schemas).to.have.property('ContactPutRequest')
554
- expect(expected).to.equal('#/components/schemas/ContactPutRequest')
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 improperly dereferenced', async function() {
558
- const simpleSchema = {
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
- const definitionGenerator = new DefinitionGenerator(mockServerless)
569
- const expected = await definitionGenerator.schemaCreator(simpleSchema, 'simpleSchema')
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('simpleSchema')
575
- expect(definitionGenerator.openAPI.components.schemas.simpleSchema).to.deep.equal({
576
- title: 'JSON API Schema',
577
- properties: {
578
- Error: {
579
- type: 'string'
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
- expect(expected).to.equal('#/components/schemas/simpleSchema')
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