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
|
|
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
|
@@ -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)) {
|
package/src/openAPIGenerator.js
CHANGED
|
@@ -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
|
|
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(
|
|
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(
|
|
123
|
+
output = JSON.stringify(validOpenAPI, null, this.config.indent);
|
|
135
124
|
break;
|
|
136
125
|
case 'yaml':
|
|
137
126
|
default:
|
|
138
|
-
output = yaml.dump(
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
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)
|