serverless-openapi-documenter 0.0.23 → 0.0.25

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
@@ -414,7 +414,7 @@ requestHeaders:
414
414
 
415
415
  ## Example configuration
416
416
 
417
- Please view the example [serverless.yml](test/serverless\ 2/serverless.yml).
417
+ Please view the example [serverless.yml](test/serverless-tests/serverless%202/serverless.yml).
418
418
 
419
419
  ## Notes on schemas
420
420
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-openapi-documenter",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -79,6 +79,8 @@ class DefinitionGenerator {
79
79
  if (event?.http?.documentation || event?.httpApi?.documentation) {
80
80
  const documentation = event?.http?.documentation || event?.httpApi?.documentation
81
81
 
82
+ this.currentFunctionName = httpFunction.functionInfo.name
83
+
82
84
  let opId
83
85
  if (this.operationIds.includes(httpFunction.functionInfo.name) === false) {
84
86
  opId = httpFunction.functionInfo.name
@@ -262,6 +264,8 @@ class DefinitionGenerator {
262
264
  description: response.responseBody.description || '',
263
265
  }
264
266
 
267
+ this.currentStatusCode = response.statusCode
268
+
265
269
  obj.content = await this.createMediaTypeObject(response.responseModels, 'responses')
266
270
  .catch(err => {
267
271
  throw err
@@ -290,6 +294,10 @@ class DefinitionGenerator {
290
294
  async createMediaTypeObject(models, type) {
291
295
  const mediaTypeObj = {}
292
296
  for (const mediaTypeDocumentation of this.serverless.service.custom.documentation.models) {
297
+ if (models === undefined || models === null) {
298
+ throw new Error(`${this.currentFunctionName} is missing a Response Model for statusCode ${this.currentStatusCode}`)
299
+ }
300
+
293
301
  if (Object.values(models).includes(mediaTypeDocumentation.name)) {
294
302
  let contentKey = ''
295
303
  for (const [key, value] of Object.entries(models)) {
@@ -107,35 +107,24 @@ class OpenAPIGenerator {
107
107
  async generate() {
108
108
  this.log(this.defaultLog, chalk.bold.underline('OpenAPI v3 Document Generation'))
109
109
  this.processCliInput()
110
- const generator = new DefinitionGenerator(this.serverless);
111
110
 
112
- await generator.parse()
111
+ const validOpenAPI = await this.generationAndValidation()
113
112
  .catch(err => {
114
- this.log('error', `ERROR: An error was thrown generating the OpenAPI v3 documentation`)
115
113
  throw new this.serverless.classes.Error(err)
116
114
  })
117
115
 
118
- const valid = await generator.validate()
119
- .catch(err => {
120
- this.log('error', `ERROR: An error was thrown validating the OpenAPI v3 documentation`)
121
- throw new this.serverless.classes.Error(err)
122
- })
123
-
124
- if (valid)
125
- this.log('success', 'OpenAPI v3 Documentation Successfully Generated')
126
-
127
116
  if (this.config.postmanCollection) {
128
- this.createPostman(generator.openAPI)
117
+ this.createPostman(validOpenAPI)
129
118
  }
130
119
 
131
120
  let output
132
121
  switch (this.config.format.toLowerCase()) {
133
122
  case 'json':
134
- output = JSON.stringify(generator.openAPI, null, this.config.indent);
123
+ output = JSON.stringify(validOpenAPI, null, this.config.indent);
135
124
  break;
136
125
  case 'yaml':
137
126
  default:
138
- output = yaml.dump(generator.openAPI, { indent: this.config.indent });
127
+ output = yaml.dump(validOpenAPI, { indent: this.config.indent });
139
128
  break;
140
129
  }
141
130
  try {
@@ -147,6 +136,28 @@ class OpenAPIGenerator {
147
136
  }
148
137
  }
149
138
 
139
+ async generationAndValidation() {
140
+ const generator = new DefinitionGenerator(this.serverless);
141
+
142
+ await generator.parse()
143
+ .catch(err => {
144
+ this.log('error', `ERROR: An error was thrown generating the OpenAPI v3 documentation`)
145
+ throw new this.serverless.classes.Error(err)
146
+ })
147
+
148
+ await generator.validate()
149
+ .catch(err => {
150
+ this.log('error', `ERROR: An error was thrown validating the OpenAPI v3 documentation`)
151
+ this.validationErrorDetails(err)
152
+ throw new this.serverless.classes.Error(err)
153
+ })
154
+
155
+
156
+ this.log('success', 'OpenAPI v3 Documentation Successfully Generated')
157
+
158
+ return generator.openAPI
159
+ }
160
+
150
161
  createPostman(openAPI) {
151
162
  const postmanGeneration = (err, result) => {
152
163
  if (err) {
@@ -205,25 +216,10 @@ class OpenAPIGenerator {
205
216
  this.config = config
206
217
  }
207
218
 
208
- validateDetails(validation) {
209
- if (validation.valid) {
210
- this.log(this.defaultLog, `${ chalk.bold.green('[VALIDATION]') } OpenAPI valid: ${chalk.bold.green('true')}\n\n`);
211
- } else {
212
- this.log(this.defaultLog, `${chalk.bold.red('[VALIDATION]')} Failed to validate OpenAPI document: \n\n`);
213
- this.log(this.defaultLog, `${chalk.bold.green('Context:')} ${JSON.stringify(validation.context, null, 2)}\n`);
214
- this.log(this.defaultLog, `${chalk.bold.green('Error Message:')} ${JSON.stringify(validation.error, null, 2)}\n`);
215
- if (typeof validation.error === 'string') {
216
- this.log(this.defaultLog, `${validation.error}\n\n`);
217
- } else {
218
- for (const info of validation.error) {
219
- this.log(this.defaultLog, chalk.grey('\n\n--------\n\n'));
220
- this.log(this.defaultLog, ' ', chalk.blue(info.dataPath), '\n');
221
- this.log(this.defaultLog, ' ', info.schemaPath, chalk.bold.yellow(info.message));
222
- this.log(this.defaultLog, chalk.grey('\n\n--------\n\n'));
223
- this.log(this.defaultLog, `${inspect(info, { colors: true, depth: 2 })}\n\n`);
224
- }
225
- }
226
- }
219
+ validationErrorDetails(validationError) {
220
+ this.log('error', `${chalk.bold.yellow('[VALIDATION]')} Failed to validate OpenAPI document: \n`);
221
+ this.log('error', `${chalk.bold.yellow('Context:')} ${JSON.stringify(validationError.options.context[validationError.options.context.length-1], null, 2)}\n`);
222
+ this.log('error', `${chalk.bold.yellow('Error Message:')} ${JSON.stringify(validationError.message, null, 2)}\n`);
227
223
  }
228
224
  }
229
225
 
@@ -0,0 +1,30 @@
1
+ {
2
+ "custom": {
3
+ "documentation": {
4
+ "title": "test-service",
5
+ "models": [
6
+ {
7
+ "name": "SuccessResponse",
8
+ "description": "Success response",
9
+ "content": {
10
+ "application/json": {
11
+ "schema": {
12
+ "$schema": "http://json-schema.org/draft-04/schema#",
13
+ "properties": {
14
+ "SomeObject": {
15
+ "type": "object",
16
+ "properties": {
17
+ "SomeAttribute": {
18
+ "type": "string"
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ ]
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "createUser": {
3
+ "name": "createUser",
4
+ "handler": "handler.create",
5
+ "events": [
6
+ {
7
+ "http": {
8
+ "path": "find/{name}",
9
+ "method": "get",
10
+ "documentation": {
11
+ "pathParams": [
12
+ {
13
+ "name": "name",
14
+ "schema": {
15
+ "type": "string"
16
+ }
17
+ }
18
+ ],
19
+ "methodResponses": [
20
+ {
21
+ "statusCode": 200,
22
+ "responseBody": {
23
+ "description": "A user object along with generated API Keys"
24
+ },
25
+ "responseModels": {
26
+ "application/json": "SuccessResponse"
27
+ }
28
+ }
29
+ ]
30
+ }
31
+ }
32
+ }
33
+ ]
34
+ }
35
+ }
@@ -7,12 +7,20 @@ const expect = require('chai').expect
7
7
 
8
8
  const validOpenAPI = require('../json/valid-openAPI.json')
9
9
 
10
+ const basicDocumentation = require('../models/BasicDocumentation.json')
11
+ const basicValidFunction = require('../models/BasicValidFunction.json')
12
+
10
13
  const OpenAPIGenerator = require('../../src/openAPIGenerator')
11
14
 
12
15
  describe('OpenAPIGenerator', () => {
13
16
  let sls, logOutput
14
17
  beforeEach(function() {
15
18
  sls = {
19
+ service: {
20
+ service: 'test-service',
21
+ getAllFunctions: () => {},
22
+ getFunction: () => {}
23
+ },
16
24
  version: '3.0.0',
17
25
  variables: {
18
26
  service: {
@@ -32,7 +40,7 @@ describe('OpenAPIGenerator', () => {
32
40
  options: {
33
41
  postmanCollection: 'postman.json'
34
42
  }
35
- }
43
+ },
36
44
  }
37
45
 
38
46
  logOutput = {
@@ -43,6 +51,119 @@ describe('OpenAPIGenerator', () => {
43
51
  }
44
52
  }
45
53
  });
54
+
55
+ describe('generationAndValidation', () => {
56
+ it('should correctly generate a valid openAPI document', async function() {
57
+ const succSpy = sinon.spy(logOutput.log, 'success')
58
+ const errSpy = sinon.spy(logOutput.log, 'error')
59
+
60
+ Object.assign(sls.service, basicDocumentation)
61
+ const getAllFuncsStub = sinon.stub(sls.service, 'getAllFunctions').returns(['createUser'])
62
+
63
+ const getFuncStub = sinon.stub(sls.service, 'getFunction').returns(basicValidFunction.createUser)
64
+
65
+ const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput)
66
+ openAPIGenerator.processCliInput()
67
+
68
+ const validOpenAPIDocument = await openAPIGenerator.generationAndValidation()
69
+ .catch(err => {
70
+ expect(err).to.be.undefined
71
+ })
72
+
73
+ expect(succSpy.called).to.be.true
74
+ expect(errSpy.called).to.be.false
75
+
76
+ succSpy.restore()
77
+ errSpy.restore()
78
+ getAllFuncsStub.reset()
79
+ getFuncStub.reset()
80
+ });
81
+
82
+ it('should throw an error when trying to generate an invalid openAPI document', async function() {
83
+ const succSpy = sinon.spy(logOutput.log, 'success')
84
+ const errSpy = sinon.spy(logOutput.log, 'error')
85
+
86
+ Object.assign(sls.service, basicDocumentation)
87
+ const getAllFuncsStub = sinon.stub(sls.service, 'getAllFunctions').returns(['createUser'])
88
+ const basicInvalidFunction = JSON.parse(JSON.stringify(basicValidFunction))
89
+
90
+ delete basicInvalidFunction.createUser.events[0].http.documentation.methodResponses[0].responseModels
91
+ const getFuncStub = sinon.stub(sls.service, 'getFunction').returns(basicInvalidFunction.createUser)
92
+
93
+ const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput)
94
+ openAPIGenerator.processCliInput()
95
+
96
+ const validOpenAPIDocument = await openAPIGenerator.generationAndValidation()
97
+ .catch(err => {
98
+ expect(err.message).to.be.equal('Error: createUser is missing a Response Model for statusCode 200')
99
+ })
100
+
101
+ expect(succSpy.called).to.be.false
102
+ expect(errSpy.called).to.be.true
103
+
104
+ succSpy.restore()
105
+ errSpy.restore()
106
+ getAllFuncsStub.reset()
107
+ getFuncStub.reset()
108
+ });
109
+
110
+ it('should correctly validate a valid openAPI document', async function() {
111
+ const succSpy = sinon.spy(logOutput.log, 'success')
112
+ const errSpy = sinon.spy(logOutput.log, 'error')
113
+
114
+ Object.assign(sls.service, basicDocumentation)
115
+ const getAllFuncsStub = sinon.stub(sls.service, 'getAllFunctions').returns(['createUser'])
116
+ const basicInvalidFunction = JSON.parse(JSON.stringify(basicValidFunction))
117
+
118
+ const getFuncStub = sinon.stub(sls.service, 'getFunction').returns(basicInvalidFunction.createUser)
119
+
120
+ const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput)
121
+ openAPIGenerator.processCliInput()
122
+
123
+ const validOpenAPIDocument = await openAPIGenerator.generationAndValidation()
124
+ .catch(err => {
125
+ expect(err).to.be.undefined
126
+ })
127
+
128
+ expect(succSpy.called).to.be.true
129
+ expect(errSpy.called).to.be.false
130
+ expect(validOpenAPIDocument).to.have.property('openapi')
131
+
132
+ succSpy.restore()
133
+ errSpy.restore()
134
+ getAllFuncsStub.reset()
135
+ getFuncStub.reset()
136
+ });
137
+
138
+ it('should throw an error when trying to validate an invalid openAPI document', async function() {
139
+ const succSpy = sinon.spy(logOutput.log, 'success')
140
+ const errSpy = sinon.spy(logOutput.log, 'error')
141
+
142
+ Object.assign(sls.service, basicDocumentation)
143
+ const getAllFuncsStub = sinon.stub(sls.service, 'getAllFunctions').returns(['createUser'])
144
+ const basicInvalidFunction = JSON.parse(JSON.stringify(basicValidFunction))
145
+
146
+ delete basicInvalidFunction.createUser.events[0].http.documentation.pathParams
147
+ const getFuncStub = sinon.stub(sls.service, 'getFunction').returns(basicInvalidFunction.createUser)
148
+
149
+ const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput)
150
+ openAPIGenerator.processCliInput()
151
+
152
+ const validOpenAPIDocument = await openAPIGenerator.generationAndValidation()
153
+ .catch(err => {
154
+ expect(err.message).to.be.equal('AssertionError: Templated parameter name not found')
155
+ })
156
+
157
+ expect(succSpy.called).to.be.false
158
+ expect(errSpy.called).to.be.true
159
+
160
+ succSpy.restore()
161
+ errSpy.restore()
162
+ getAllFuncsStub.reset()
163
+ getFuncStub.reset()
164
+ });
165
+ });
166
+
46
167
  describe('createPostman', () => {
47
168
  it('should generate a postman collection when a valid openAPI file is generated', function() {
48
169
  const fsStub = sinon.stub(fs, 'writeFileSync').returns(true)