serverless-openapi-documenter 0.0.17 → 0.0.20

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
@@ -7,9 +7,12 @@
7
7
  <a href="https://www.npmjs.com/package/serverless-openapi-documenter">
8
8
  <img src="https://img.shields.io/npm/v/serverless-openapi-documenter.svg?style=flat-square">
9
9
  </a>
10
+ <a href="https://github.com/JaredCE/serverless-openapi-documenter/actions/workflows/node.yml">
11
+ <img src="https://github.com/JaredCE/serverless-openapi-documenter/actions/workflows/node.yml/badge.svg">
12
+ </a>
10
13
  </p>
11
14
 
12
- ![node.js tests](https://github.com/JaredCE/serverless-openapi-documenter/actions/workflows/node.js.yml/badge.svg)
15
+
13
16
 
14
17
  This will generate an OpenAPI V3 (up to v3.0.3) file for you from your serverless file. It can optionally generate a Postman Collection V2 from the OpenAPI file for you too.
15
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-openapi-documenter",
3
- "version": "0.0.17",
3
+ "version": "0.0.20",
4
4
  "description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -31,9 +31,9 @@
31
31
  "@apidevtools/json-schema-ref-parser": "^9.0.9",
32
32
  "chalk": "^4.1.2",
33
33
  "js-yaml": "^4.1.0",
34
- "json-schema-for-openapi": "^0.1.5",
34
+ "json-schema-for-openapi": "^0.2.0",
35
35
  "oas-validator": "^5.0.8",
36
- "openapi-to-postmanv2": "^3.2.0",
36
+ "openapi-to-postmanv2": "^4.1.0",
37
37
  "swagger2openapi": "^7.0.8",
38
38
  "uuid": "^8.3.2"
39
39
  },
@@ -134,6 +134,10 @@ class DefinitionGenerator {
134
134
  obj.description = server.description
135
135
  }
136
136
 
137
+ if (server.variables) {
138
+ obj.variables = server.variables
139
+ }
140
+
137
141
  newServers.push(obj)
138
142
  }
139
143
  } else {
@@ -145,6 +149,10 @@ class DefinitionGenerator {
145
149
  obj.description = servers.description
146
150
  }
147
151
 
152
+ if (servers.variables) {
153
+ obj.variables = servers.variables
154
+ }
155
+
148
156
  newServers.push(obj)
149
157
  }
150
158
 
@@ -106,7 +106,7 @@ class OpenAPIGenerator {
106
106
 
107
107
  async generate() {
108
108
  this.log(this.defaultLog, chalk.bold.underline('OpenAPI v3 Document Generation'))
109
- const config = this.processCliInput()
109
+ this.processCliInput()
110
110
  const generator = new DefinitionGenerator(this.serverless);
111
111
 
112
112
  await generator.parse()
@@ -124,42 +124,22 @@ class OpenAPIGenerator {
124
124
  if (valid)
125
125
  this.log('success', 'OpenAPI v3 Documentation Successfully Generated')
126
126
 
127
- if (config.postmanCollection) {
128
- const postmanGeneration = (err, result) => {
129
- if (err) {
130
- this.log('error', `ERROR: An error was thrown when generating the postman collection`)
131
- throw new this.serverless.classes.Error(err)
132
- }
133
-
134
- this.log('success', 'postman collection v2 Documentation Successfully Generated')
135
- try {
136
- fs.writeFileSync(config.postmanCollection, JSON.stringify(result.output[0].data))
137
- this.log('success', 'postman collection v2 Documentation Successfully Written')
138
- } catch (err) {
139
- this.log('error', `ERROR: An error was thrown whilst writing the postman collection`)
140
- throw new this.serverless.classes.Error(err)
141
- }
142
- }
143
-
144
- const postmanCollection = PostmanGenerator.convert(
145
- {type: 'json', data: JSON.parse(JSON.stringify(generator.openAPI))},
146
- {},
147
- postmanGeneration
148
- )
127
+ if (this.config.postmanCollection) {
128
+ this.createPostman(generator.openAPI)
149
129
  }
150
130
 
151
131
  let output
152
- switch (config.format.toLowerCase()) {
132
+ switch (this.config.format.toLowerCase()) {
153
133
  case 'json':
154
- output = JSON.stringify(generator.openAPI, null, config.indent);
134
+ output = JSON.stringify(generator.openAPI, null, this.config.indent);
155
135
  break;
156
136
  case 'yaml':
157
137
  default:
158
- output = yaml.dump(generator.openAPI, { indent: config.indent });
138
+ output = yaml.dump(generator.openAPI, { indent: this.config.indent });
159
139
  break;
160
140
  }
161
141
  try {
162
- fs.writeFileSync(config.file, output);
142
+ fs.writeFileSync(this.config.file, output);
163
143
  this.log('success', 'OpenAPI v3 Documentation Successfully Written')
164
144
  } catch (err) {
165
145
  this.log('error', `ERROR: An error was thrown whilst writing the openAPI Documentation`)
@@ -167,6 +147,30 @@ class OpenAPIGenerator {
167
147
  }
168
148
  }
169
149
 
150
+ createPostman(openAPI) {
151
+ const postmanGeneration = (err, result) => {
152
+ if (err) {
153
+ this.log('error', `ERROR: An error was thrown when generating the postman collection`)
154
+ throw new this.serverless.classes.Error(err)
155
+ }
156
+
157
+ this.log('success', 'postman collection v2 Documentation Successfully Generated')
158
+ try {
159
+ fs.writeFileSync(this.config.postmanCollection, JSON.stringify(result.output[0].data))
160
+ this.log('success', 'postman collection v2 Documentation Successfully Written')
161
+ } catch (err) {
162
+ this.log('error', `ERROR: An error was thrown whilst writing the postman collection`)
163
+ throw new this.serverless.classes.Error(err)
164
+ }
165
+ }
166
+
167
+ PostmanGenerator.convert(
168
+ {type: 'json', data: JSON.parse(JSON.stringify(openAPI))},
169
+ {},
170
+ postmanGeneration
171
+ )
172
+ }
173
+
170
174
  processCliInput () {
171
175
  const config = {
172
176
  format: 'json',
@@ -182,7 +186,6 @@ class OpenAPIGenerator {
182
186
  config.postmanCollection = this.serverless.processedInput.options.postmanCollection || null
183
187
 
184
188
  if (['yaml', 'json'].indexOf(config.format.toLowerCase()) < 0) {
185
- // throw new Error('Invalid Output Format Specified - must be one of "yaml" or "json"');
186
189
  throw new this.serverless.classes.Error('Invalid Output Format Specified - must be one of "yaml" or "json"')
187
190
  }
188
191
 
@@ -199,7 +202,7 @@ class OpenAPIGenerator {
199
202
  ${config.postmanCollection ? `postman collection: ${chalk.bold.green(config.postmanCollection)}`: `\n\n`}`
200
203
  )
201
204
 
202
- return config
205
+ this.config = config
203
206
  }
204
207
 
205
208
  validateDetails(validation) {
@@ -0,0 +1,274 @@
1
+ {
2
+ "openapi": "3.0.3",
3
+ "info": {
4
+ "title": "serverless-openapi-doc-demo",
5
+ "description": "This is a description of what this does",
6
+ "version": "1.0.0"
7
+ },
8
+ "components": {
9
+ "schemas": {
10
+ "username": {
11
+ "type": "string",
12
+ "pattern": "^[-a-z0-9_]+$"
13
+ },
14
+ "membershipType": {
15
+ "type": "string",
16
+ "enum": [
17
+ "premium",
18
+ "standard"
19
+ ]
20
+ },
21
+ "SessionId": {
22
+ "type": "string"
23
+ },
24
+ "PutDocumentRequest": {
25
+ "properties": {
26
+ "SomeObject": {
27
+ "type": "object",
28
+ "properties": {
29
+ "SomeAttribute": {
30
+ "type": "string"
31
+ }
32
+ }
33
+ }
34
+ }
35
+ },
36
+ "PutDocumentResponse": {
37
+ "title": "Empty Schema",
38
+ "type": "object"
39
+ },
40
+ "error": {
41
+ "type": "object",
42
+ "properties": {
43
+ "id": {
44
+ "description": "A unique identifier for this particular occurrence of the problem.",
45
+ "type": "string"
46
+ },
47
+ "links": {
48
+ "$ref": "#/components/schemas/links"
49
+ },
50
+ "status": {
51
+ "description": "The HTTP status code applicable to this problem, expressed as a string value.",
52
+ "type": "string"
53
+ },
54
+ "code": {
55
+ "description": "An application-specific error code, expressed as a string value.",
56
+ "type": "string"
57
+ },
58
+ "title": {
59
+ "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.",
60
+ "type": "string"
61
+ },
62
+ "detail": {
63
+ "description": "A human-readable explanation specific to this occurrence of the problem.",
64
+ "type": "string"
65
+ },
66
+ "source": {
67
+ "type": "object",
68
+ "properties": {
69
+ "pointer": {
70
+ "description": "A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].",
71
+ "type": "string"
72
+ },
73
+ "parameter": {
74
+ "description": "A string indicating which query parameter caused the error.",
75
+ "type": "string"
76
+ }
77
+ }
78
+ },
79
+ "meta": {
80
+ "$ref": "#/components/schemas/meta"
81
+ }
82
+ },
83
+ "additionalProperties": false
84
+ },
85
+ "meta": {
86
+ "description": "Non-standard meta-information that can not be represented as an attribute or relationship.",
87
+ "type": "object",
88
+ "additionalProperties": true
89
+ },
90
+ "links": {
91
+ "description": "A resource object **MAY** contain references to other resource objects (\"relationships\"). Relationships may be to-one or to-many. Relationships can be specified by including a member in a resource's links object.",
92
+ "type": "object",
93
+ "properties": {
94
+ "self": {
95
+ "description": "A `self` member, whose value is a URL for the relationship itself (a \"relationship URL\"). This URL allows the client to directly manipulate the relationship. For example, it would allow a client to remove an `author` from an `article` without deleting the people resource itself.",
96
+ "type": "string",
97
+ "format": "uri"
98
+ },
99
+ "related": {
100
+ "$ref": "#/components/schemas/link"
101
+ }
102
+ },
103
+ "additionalProperties": true
104
+ },
105
+ "link": {
106
+ "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.",
107
+ "oneOf": [
108
+ {
109
+ "description": "A string containing the link's URL.",
110
+ "type": "string",
111
+ "format": "uri"
112
+ },
113
+ {
114
+ "type": "object",
115
+ "required": [
116
+ "href"
117
+ ],
118
+ "properties": {
119
+ "href": {
120
+ "description": "A string containing the link's URL.",
121
+ "type": "string",
122
+ "format": "uri"
123
+ },
124
+ "meta": {
125
+ "$ref": "#/components/schemas/meta"
126
+ }
127
+ }
128
+ }
129
+ ]
130
+ },
131
+ "ErrorResponse": {
132
+ "title": "JSON API Schema",
133
+ "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org",
134
+ "type": "object",
135
+ "required": [
136
+ "errors"
137
+ ],
138
+ "properties": {
139
+ "errors": {
140
+ "type": "array",
141
+ "items": {
142
+ "$ref": "#/components/schemas/error"
143
+ },
144
+ "uniqueItems": true
145
+ },
146
+ "meta": {
147
+ "$ref": "#/components/schemas/meta"
148
+ },
149
+ "links": {
150
+ "$ref": "#/components/schemas/links"
151
+ }
152
+ },
153
+ "additionalProperties": false
154
+ }
155
+ }
156
+ },
157
+ "paths": {
158
+ "/create/{username}": {
159
+ "post": {
160
+ "summary": "Create User",
161
+ "description": "Creates a user and then sends a generated password email",
162
+ "operationId": "serverless-openapi-doc-demo-dev-createUser",
163
+ "parameters": [
164
+ {
165
+ "name": "username",
166
+ "in": "path",
167
+ "description": "The username for a user to create",
168
+ "required": true,
169
+ "schema": {
170
+ "$ref": "#/components/schemas/username"
171
+ }
172
+ },
173
+ {
174
+ "name": "membershipType",
175
+ "in": "query",
176
+ "description": "The user's Membership Type",
177
+ "required": false,
178
+ "schema": {
179
+ "$ref": "#/components/schemas/membershipType"
180
+ }
181
+ },
182
+ {
183
+ "name": "SessionId",
184
+ "in": "cookie",
185
+ "description": "A Session ID variable",
186
+ "required": false,
187
+ "schema": {
188
+ "$ref": "#/components/schemas/SessionId"
189
+ }
190
+ }
191
+ ],
192
+ "tags": [
193
+ "jesus"
194
+ ],
195
+ "externalDocs": {
196
+ "url": "https://bing.com",
197
+ "description": "A link to bing"
198
+ },
199
+ "requestBody": {
200
+ "description": "A user information object",
201
+ "required": false,
202
+ "content": {
203
+ "application/json": {
204
+ "schema": {
205
+ "$ref": "#/components/schemas/PutDocumentRequest"
206
+ }
207
+ }
208
+ }
209
+ },
210
+ "responses": {
211
+ "201": {
212
+ "description": "A user object along with generated API Keys",
213
+ "content": {
214
+ "application/json": {
215
+ "schema": {
216
+ "$ref": "#/components/schemas/PutDocumentResponse"
217
+ }
218
+ }
219
+ }
220
+ },
221
+ "500": {
222
+ "description": "An error message when creating a new user",
223
+ "content": {
224
+ "application/json": {
225
+ "schema": {
226
+ "$ref": "#/components/schemas/ErrorResponse"
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ },
233
+ "summary": "a function",
234
+ "description": "blah blah"
235
+ },
236
+ "/patch/": {
237
+ "patch": {
238
+ "summary": "Patch a User",
239
+ "description": "Patch details about the user",
240
+ "operationId": "serverless-openapi-doc-demo-dev-patchUser",
241
+ "parameters": [],
242
+ "tags": [
243
+ "patching"
244
+ ],
245
+ "responses": {
246
+ "200": {
247
+ "description": "A user object along with generated API Keys",
248
+ "content": {
249
+ "application/json": {
250
+ "schema": {
251
+ "$ref": "#/components/schemas/PutDocumentResponse"
252
+ }
253
+ }
254
+ }
255
+ }
256
+ }
257
+ }
258
+ }
259
+ },
260
+ "tags": [
261
+ {
262
+ "name": "jesus",
263
+ "description": "jesus was a man",
264
+ "externalDocs": {
265
+ "url": "https://whitehouse.gov",
266
+ "description": "a link to the whitehouse"
267
+ }
268
+ }
269
+ ],
270
+ "externalDocs": {
271
+ "url": "https://google.com",
272
+ "description": "A link to google"
273
+ }
274
+ }
@@ -1,10 +1,101 @@
1
1
  'use strict'
2
2
 
3
+ const fs = require('fs')
4
+ const PostmanGenerator = require('openapi-to-postmanv2')
3
5
  const sinon = require('sinon')
4
6
  const expect = require('chai').expect
5
7
 
8
+ const validOpenAPI = require('../json/valid-openAPI.json')
9
+
6
10
  const OpenAPIGenerator = require('../../src/openAPIGenerator')
7
11
 
8
- xdescribe('OpenAPIGenerator', () => {
12
+ describe('OpenAPIGenerator', () => {
13
+ let sls, logOutput
14
+ beforeEach(function() {
15
+ sls = {
16
+ version: '3.0.0',
17
+ variables: {
18
+ service: {
19
+ custom: {
20
+
21
+ }
22
+ }
23
+ },
24
+ configSchemaHandler: {
25
+ defineFunctionEventProperties: () => {},
26
+ defineFunctionProperties: () => {}
27
+ },
28
+ classes: {
29
+ Error: class ServerlessError {constructor(err) {return new Error(err)}}
30
+ },
31
+ processedInput: {
32
+ options: {
33
+ postmanCollection: 'postman.json'
34
+ }
35
+ }
36
+ }
37
+
38
+ logOutput = {
39
+ log: {
40
+ notice: (str) => {},
41
+ error: (str) => {},
42
+ success: (str) => {}
43
+ }
44
+ }
45
+ });
46
+ describe('createPostman', () => {
47
+ it('should generate a postman collection when a valid openAPI file is generated', function() {
48
+ const fsStub = sinon.stub(fs, 'writeFileSync').returns(true)
49
+ const succSpy = sinon.spy(logOutput.log, 'success')
50
+ const errSpy = sinon.spy(logOutput.log, 'error')
51
+ const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput)
52
+ openAPIGenerator.processCliInput()
53
+
54
+ openAPIGenerator.createPostman(validOpenAPI)
55
+
56
+ expect(fsStub.called).to.be.true
57
+ expect(succSpy.calledTwice).to.be.true
58
+ expect(errSpy.called).to.be.false
59
+ fsStub.restore()
60
+ succSpy.restore()
61
+ errSpy.restore()
62
+ });
63
+
64
+ it('should throw an error when writing a file fails', function() {
65
+ const errStub = sinon.stub(logOutput.log, 'error').returns('')
66
+ const succSpy = sinon.spy(logOutput.log, 'success')
67
+ const fsStub = sinon.stub(fs, 'writeFileSync').throws(new Error())
68
+ const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput)
69
+ openAPIGenerator.processCliInput()
70
+
71
+ expect(() => {openAPIGenerator.createPostman(validOpenAPI)}).to.throw()
72
+
73
+ expect(fsStub.called).to.be.true
74
+ expect(errStub.called).to.be.true
75
+ expect(succSpy.calledOnce).to.be.true
76
+ expect(succSpy.calledTwice).to.be.false
77
+ fsStub.restore()
78
+ succSpy.restore()
79
+ errStub.restore()
80
+ });
81
+
82
+ it('should throw an error converting an OpenAPI fails', function() {
83
+ const errStub = sinon.spy(logOutput.log, 'error')
84
+ const succSpy = sinon.spy(logOutput.log, 'success')
85
+ const pgStub = sinon.stub(PostmanGenerator, 'convert')
86
+ pgStub.yields(new Error())
87
+
88
+ const openAPIGenerator = new OpenAPIGenerator(sls, {}, logOutput)
89
+ openAPIGenerator.processCliInput()
90
+
91
+ expect(() => {openAPIGenerator.createPostman(validOpenAPI)}).to.throw()
92
+
93
+ expect(errStub.called).to.be.true
94
+ expect(succSpy.calledOnce).to.be.false
95
+ expect(succSpy.calledTwice).to.be.false
9
96
 
97
+ succSpy.restore()
98
+ errStub.restore()
99
+ });
100
+ });
10
101
  });