serverless-openapi-documenter 0.0.120-beta.1 → 0.0.123

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
@@ -12,9 +12,7 @@
12
12
  </a>
13
13
  </p>
14
14
 
15
- This will generate an [OpenAPI V3](https://spec.openapis.org/oas/v3.0.0.html) (up to v3.0.4) specification file for you from your serverless file. It can optionally generate a [Postman Collection V2](https://github.com/postmanlabs/openapi-to-postman) from the OpenAPI file for you too. This currently works for `http` and `httpApi` configurations.
16
-
17
- If you are using the beta of 0.0.115, it will now try and create [OpenAPI V3.1 (3.1.x)](https://spec.openapis.org/oas/v3.1.0.html) specification file for you, should you run the command `serverless openapi generate -o openapi.json -f json -a 3.1.1 -p postman.json`. Please see this [guide on migrating to V3.1](https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0). Whilst I perosnally use this plugin all the time, please do open and report bugs, and I will do my best to fix them.
15
+ This will generate an OpenAPI V3 (up to v3.0.4) file for you from your serverless file. It can optionally generate a [Postman Collection V2](https://github.com/postmanlabs/openapi-to-postman) or (as of 0.0.120) [Bruno Collection](https://docs.usebruno.com/) from the OpenAPI file. This currently works for `http` and `httpApi` configurations.
18
16
 
19
17
  Originally based off of: https://github.com/temando/serverless-openapi-documentation
20
18
 
@@ -51,6 +49,7 @@ Options:
51
49
  --indent -i File indentation in spaces. Default: 2
52
50
  --openApiVersion -a OpenAPI version to generate for. Default: 3.0.0
53
51
  --postmanCollection -p Will generate a postman collection (from the generated OpenAPI Description), in json only, if passed in. Default: postman.json
52
+ --brunoCollection -b Will generate a Bruno collection (from the generated OpenAPI Description), in json only, if passed in. Default: bruno.json
54
53
  --validationWarn -w Warn about validation errors only. Will write the OpenAPI file if generation is successful. Default: false
55
54
  ```
56
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-openapi-documenter",
3
- "version": "0.0.120-beta.1",
3
+ "version": "0.0.123",
4
4
  "description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -22,6 +22,9 @@
22
22
  "PostmanCollections",
23
23
  "Postman-Collections",
24
24
  "Postman Collections",
25
+ "Bruno Collection",
26
+ "Bruno",
27
+ "Use Bruno",
25
28
  "AWS",
26
29
  "AWS APIGateway",
27
30
  "Api Gateway",
@@ -46,11 +49,12 @@
46
49
  "license": "MIT",
47
50
  "dependencies": {
48
51
  "@apidevtools/json-schema-ref-parser": "^9.1.0",
49
- "@redocly/openapi-core": "^1.34.5",
52
+ "@redocly/openapi-core": "^1.2.0",
53
+ "@usebruno/converters": "^0.16.0",
50
54
  "chalk": "^4.1.2",
51
55
  "js-yaml": "^4.1.1",
52
56
  "json-schema-for-openapi": "^0.5.0",
53
- "openapi-to-postmanv2": "^5.6.0",
57
+ "openapi-to-postmanv2": "^6.0.0",
54
58
  "uuid": "^11.1.0"
55
59
  },
56
60
  "engines": {
package/src/bruno.js ADDED
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const { openApiToBruno } = require('@usebruno/converters');
4
+
5
+ const fs = require('fs/promises')
6
+
7
+ class Bruno {
8
+ constructor(output, serverless, logger) {
9
+ this.output = output;
10
+ this.logger = logger;
11
+ this.serverless = serverless;
12
+ }
13
+
14
+ async create(openAPI) {
15
+ try {
16
+ const brunoCollection = openApiToBruno(openAPI);
17
+
18
+ await fs.writeFile(this.output, JSON.stringify(brunoCollection, null, 2));
19
+ this.logger.success(
20
+ "Bruno collection Documentation Successfully Written"
21
+ );
22
+ } catch (error) {
23
+ this.logger.error(
24
+ `ERROR: An error was thrown whilst writing the Bruno collection`
25
+ );
26
+
27
+ throw new this.serverless.classes.Error(error);
28
+ }
29
+ }
30
+ }
31
+
32
+ module.exports = Bruno;
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const Bruno = require('./bruno');
4
+ const Postman = require('./postman');
5
+
6
+ class CollectionFactory {
7
+ constructor(outputFile, serverless, logger) {
8
+ this.outputFile = outputFile;
9
+ this.serverless = serverless;
10
+ this.logger = logger;
11
+ }
12
+
13
+ createCollection(type) {
14
+ let creator;
15
+ switch (type){
16
+ case 'bruno':
17
+ creator = new Bruno(this.outputFile, this.serverless, this.logger);
18
+ break;
19
+
20
+ case 'postman':
21
+ creator = new Postman(this.outputFile, this.serverless, this.logger);
22
+ break;
23
+ }
24
+
25
+ return creator;
26
+ }
27
+ }
28
+
29
+ module.exports = CollectionFactory;
@@ -167,11 +167,12 @@ class DefinitionGenerator {
167
167
 
168
168
  if (documentation.contact) {
169
169
  const contactObj = {};
170
- contactObj.name = documentation.contact.name || "";
170
+
171
+ if (documentation.contact.name) contactObj.name = documentation.contact.name;
171
172
 
172
173
  if (documentation.contact.url) contactObj.url = documentation.contact.url;
173
174
 
174
- contactObj.email = documentation.contact.email || "";
175
+ if (documentation.contact.email) contactObj.email = documentation.contact.email;
175
176
 
176
177
  const extendedSpec = this.extendSpecification(documentation.contact);
177
178
 
@@ -591,7 +592,9 @@ class DefinitionGenerator {
591
592
  obj.headers = corsHeaders;
592
593
  addHeaders(owaspHeaders);
593
594
  } else {
594
- obj.headers = owaspHeaders;
595
+ if (Object.keys(owaspHeaders).length) {
596
+ obj.headers = owaspHeaders;
597
+ }
595
598
  }
596
599
  }
597
600
 
@@ -677,10 +680,13 @@ class DefinitionGenerator {
677
680
 
678
681
  async createRequestBody(requestBodyDetails) {
679
682
  const obj = {
680
- description: requestBodyDetails.description,
681
683
  required: requestBodyDetails.required || false,
682
684
  };
683
685
 
686
+ if (requestBodyDetails.description) {
687
+ obj.description = requestBodyDetails.description;
688
+ }
689
+
684
690
  obj.content = await this.createMediaTypeObject(
685
691
  requestBodyDetails.models
686
692
  ).catch((err) => {
@@ -692,7 +698,6 @@ class DefinitionGenerator {
692
698
 
693
699
  async createMediaTypeObject(models, type) {
694
700
  const mediaTypeObj = {};
695
-
696
701
  for (const mediaTypeDocumentation of this.schemaHandler.models) {
697
702
  if (models === undefined || models === null) {
698
703
  throw new Error(
@@ -700,48 +705,53 @@ class DefinitionGenerator {
700
705
  );
701
706
  }
702
707
 
703
- if (Object.values(models).includes(mediaTypeDocumentation.name)) {
704
- let contentKey = "";
705
- for (const [key, value] of Object.entries(models)) {
706
- if (value === mediaTypeDocumentation.name) contentKey = key;
708
+ for (const modelContentType in models) {
709
+ let contentKey
710
+
711
+ if (models[modelContentType] === mediaTypeDocumentation.name) {
712
+ contentKey = modelContentType;
707
713
  }
708
- const obj = {};
709
714
 
710
- let schema;
711
- if (mediaTypeDocumentation?.content) {
712
- if (mediaTypeDocumentation.content[contentKey]?.example)
713
- obj.example = mediaTypeDocumentation.content[contentKey].example;
715
+ if (contentKey) {
714
716
 
715
- if (mediaTypeDocumentation.content[contentKey]?.examples)
716
- obj.examples = this.createExamples(
717
- mediaTypeDocumentation.content[contentKey].examples
718
- );
717
+ const obj = {};
718
+ let schema;
719
+ if (mediaTypeDocumentation.content) {
720
+ if (mediaTypeDocumentation.content[contentKey]?.example) {
721
+ obj.example = mediaTypeDocumentation.content[contentKey]?.example;
722
+ }
719
723
 
720
- schema = mediaTypeDocumentation.content[contentKey].schema;
721
- } else if (
722
- mediaTypeDocumentation?.contentType &&
723
- mediaTypeDocumentation.schema
724
- ) {
725
- if (mediaTypeDocumentation?.example)
726
- obj.example = mediaTypeDocumentation.example;
724
+ if (mediaTypeDocumentation.content[contentKey]?.examples) {
725
+ obj.examples = this.createExamples(
726
+ mediaTypeDocumentation.content[contentKey].examples
727
+ );
728
+ }
727
729
 
728
- if (mediaTypeDocumentation?.examples)
729
- obj.examples = this.createExamples(mediaTypeDocumentation.examples);
730
+ schema = (mediaTypeDocumentation.schema) ? mediaTypeDocumentation.schema : mediaTypeDocumentation.schemas[contentKey];
731
+ } else if (mediaTypeDocumentation?.contentType && mediaTypeDocumentation.schema) {
732
+ if (mediaTypeDocumentation.example) {
733
+ obj.example = mediaTypeDocumentation.example;
734
+ }
730
735
 
731
- schema = mediaTypeDocumentation.schema;
732
- }
736
+ if (mediaTypeDocumentation.examples) {
737
+ obj.example = mediaTypeDocumentation.examples;
738
+ }
733
739
 
734
- const schemaRef = await this.schemaHandler
735
- .createSchema(mediaTypeDocumentation.name)
736
- .catch((err) => {
737
- throw err;
738
- });
740
+ schema = mediaTypeDocumentation.schema;
741
+ }
739
742
 
740
- obj.schema = {
741
- $ref: schemaRef,
742
- };
743
+ const schemaRef = await this.schemaHandler
744
+ .createSchema(mediaTypeDocumentation.name)
745
+ .catch((err) => {
746
+ throw err;
747
+ });
743
748
 
744
- Object.assign(mediaTypeObj, { [contentKey]: obj });
749
+ obj.schema = {
750
+ $ref: schemaRef,
751
+ };
752
+
753
+ Object.assign(mediaTypeObj, { [contentKey]: obj });
754
+ }
745
755
  }
746
756
  }
747
757
 
@@ -861,7 +871,7 @@ class DefinitionGenerator {
861
871
  if (
862
872
  this.openAPI.components[type][name] &&
863
873
  isEqual(schemaObj[name], this.openAPI.components[type][name]) ===
864
- false
874
+ false
865
875
  ) {
866
876
  delete schemaObj[name];
867
877
  newName = `${name}-${uuid()}`;
@@ -4,9 +4,9 @@ const fs = require("fs");
4
4
  const yaml = require("js-yaml");
5
5
  const chalk = require("chalk");
6
6
 
7
+ const Collection = require('./collection')
7
8
  const DefinitionGenerator = require("./definitionGenerator");
8
9
  const Logger = require("./logger");
9
- const PostmanGenerator = require("openapi-to-postmanv2");
10
10
 
11
11
  class OpenAPIGenerator {
12
12
  constructor(serverless, options, { log = {} } = {}) {
@@ -44,10 +44,16 @@ class OpenAPIGenerator {
44
44
  },
45
45
  postmanCollection: {
46
46
  usage:
47
- "Output a postman collection and attach to OpenApi external documents [default: postman.json if passed]",
47
+ "Output a Postman collection and attach to OpenApi external documents [default: postman.json if passed]",
48
48
  shortcut: "p",
49
49
  type: "string",
50
50
  },
51
+ brunoCollection: {
52
+ usage:
53
+ "Output a Bruno collection and attach to OpenApi external documents [default: bruno.json if passed]",
54
+ shortcut: "b",
55
+ type: "string",
56
+ },
51
57
  validationWarn: {
52
58
  usage:
53
59
  "Only warn about validation errors of the OpenAPI Description, write the file if parsing is successful [default: false]",
@@ -144,8 +150,9 @@ class OpenAPIGenerator {
144
150
  throw new this.serverless.classes.Error(err);
145
151
  });
146
152
 
147
- if (this.config.postmanCollection) {
148
- this.createPostman(validOpenAPI);
153
+ if (this.shouldCreateCollection()) {
154
+ await this.createCollection(validOpenAPI)
155
+
149
156
  }
150
157
 
151
158
  let output;
@@ -210,41 +217,21 @@ class OpenAPIGenerator {
210
217
  return generator.openAPI;
211
218
  }
212
219
 
213
- createPostman(openAPI) {
214
- const postmanGeneration = (err, result) => {
215
- if (err) {
216
- this.logger.error(
217
- `ERROR: An error was thrown when generating the postman collection`
218
- );
219
- throw new this.serverless.classes.Error(err);
220
- }
220
+ async createCollection(validOpenAPI) {
221
+ const collectionOutputFile = this.config.postmanCollection || this.config.brunoCollection
222
+ const collection = new Collection(collectionOutputFile, this.serverless, this.logger)
223
+ const collectionCreator = collection.createCollection(this.collectionType())
221
224
 
222
- this.logger.success(
223
- "postman collection v2 Documentation Successfully Generated"
224
- );
225
+ await collectionCreator.create(validOpenAPI)
226
+ }
225
227
 
226
- try {
227
- fs.writeFileSync(
228
- this.config.postmanCollection,
229
- JSON.stringify(result.output[0].data)
230
- );
231
- this.logger.success(
232
- "postman collection v2 Documentation Successfully Written"
233
- );
234
- } catch (err) {
235
- this.logger.error(
236
- `ERROR: An error was thrown whilst writing the postman collection`
237
- );
238
-
239
- throw new this.serverless.classes.Error(err);
240
- }
241
- };
228
+ shouldCreateCollection() {
229
+ return Boolean(this.config.postmanCollection || this.config.brunoCollection)
230
+ }
242
231
 
243
- PostmanGenerator.convert(
244
- { type: "json", data: structuredClone(openAPI) },
245
- {},
246
- postmanGeneration
247
- );
232
+ collectionType() {
233
+ if (this.config.postmanCollection) return 'postman'
234
+ else return 'bruno'
248
235
  }
249
236
 
250
237
  processCliInput() {
@@ -254,6 +241,7 @@ class OpenAPIGenerator {
254
241
  indent: 2,
255
242
  openApiVersion: "3.0.0",
256
243
  postmanCollection: "postman.json",
244
+ brunoCollection: "bruno.json",
257
245
  validationWarn: false,
258
246
  };
259
247
 
@@ -263,6 +251,8 @@ class OpenAPIGenerator {
263
251
  this.serverless.processedInput.options.openApiVersion || "3.0.0";
264
252
  config.postmanCollection =
265
253
  this.serverless.processedInput.options.postmanCollection || null;
254
+ config.brunoCollection =
255
+ this.serverless.processedInput.options.brunoCollection || null;
266
256
  config.validationWarn =
267
257
  this.serverless.processedInput.options.validationWarn || false;
268
258
 
@@ -285,7 +275,12 @@ class OpenAPIGenerator {
285
275
  validationWarn: ${chalk.bold.green(String(config.validationWarn))}
286
276
  ${
287
277
  config.postmanCollection
288
- ? `postman collection: ${chalk.bold.green(config.postmanCollection)}`
278
+ ? `Postman collection: ${chalk.bold.green(config.postmanCollection)}`
279
+ : `\n\n`
280
+ }
281
+ ${
282
+ config.brunoCollection
283
+ ? `Bruno collection: ${chalk.bold.green(config.brunoCollection)}`
289
284
  : `\n\n`
290
285
  }`
291
286
  );
package/src/postman.js ADDED
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ const PostmanGenerator = require("openapi-to-postmanv2");
4
+
5
+ const fs = require('fs');
6
+
7
+ class Postman {
8
+ constructor(output, serverless, logger) {
9
+ this.output = output;
10
+ this.logger = logger;
11
+ this.serverless = serverless;
12
+ }
13
+
14
+ create(openAPI) {
15
+ const postmanGeneration = (err, result) => {
16
+ if (err) {
17
+ this.logger.error(
18
+ `ERROR: An error was thrown when generating the Postman collection`
19
+ );
20
+ throw new this.serverless.classes.Error(err);
21
+ }
22
+
23
+ this.logger.success(
24
+ "Postman collection v2 Documentation Successfully Generated"
25
+ );
26
+
27
+ try {
28
+ fs.writeFileSync(
29
+ this.output,
30
+ JSON.stringify(result.output[0].data)
31
+ );
32
+
33
+ this.logger.success(
34
+ "Postman collection v2 Documentation Successfully Written"
35
+ );
36
+ } catch (err) {
37
+ this.logger.error(
38
+ `ERROR: An error was thrown whilst writing the Postman collection`
39
+ );
40
+
41
+ throw new this.serverless.classes.Error(err);
42
+ }
43
+ };
44
+
45
+ PostmanGenerator.convert(
46
+ { type: "json", data: structuredClone(openAPI) },
47
+ {},
48
+ postmanGeneration
49
+ );
50
+ }
51
+ }
52
+
53
+ module.exports = Postman;
@@ -16,12 +16,6 @@ class SchemaHandler {
16
16
  this.documentation = serverless.service.custom.documentation;
17
17
  this.openAPI = openAPI;
18
18
 
19
- this.shouldConvert = true;
20
- if (/(3\.1\.\d)/g.test(this.openAPI.openapi)) this.shouldConvert = false;
21
-
22
- this.logger.verbose(`OpenAPI version: ${this.openAPI.openapi}`);
23
- this.logger.verbose(`Convert Schemas: ${this.shouldConvert}`);
24
-
25
19
  this.modelReferences = {};
26
20
 
27
21
  this.__standardiseModels();
@@ -48,9 +42,21 @@ class SchemaHandler {
48
42
  return model;
49
43
  }
50
44
 
51
- const contentType = Object.keys(model.content)[0];
52
- model.contentType = contentType;
53
- model.schema = model.content[contentType].schema;
45
+ if (Object.keys(model.content).length === 1) {
46
+ const contentType = Object.keys(model.content)[0];
47
+ model.contentType = contentType;
48
+ model.contentTypes = [contentType];
49
+ model.schema = model.content[contentType].schema;
50
+ } else {
51
+ model.contentType = null;
52
+ model.contentTypes = Object.keys(model.content);
53
+ model.schema = null;
54
+ model.schemas = {};
55
+ for (const key in model.content) {
56
+ Object.assign(model.schemas, {[key]: {schema: model.content[key].schema}});
57
+ }
58
+ // model.schema = model.content[contentType].schema;
59
+ }
54
60
 
55
61
  return model;
56
62
  };
@@ -75,43 +81,53 @@ class SchemaHandler {
75
81
  async addModelsToOpenAPI() {
76
82
  for (const model of this.models) {
77
83
  const modelName = model.name;
78
- const modelSchema = model.schema;
84
+ const schemas = []
85
+ if (model.schema){
86
+ // const modelSchema = model.schema;
87
+ schemas.push(model.schema)
88
+ } else {
89
+ for (const key in model.schemas) {
90
+ schemas.push(model.schemas[key].schema);
91
+ }
92
+ }
79
93
 
80
- const convertedSchemas = await this.__dereferenceAndConvert(
81
- modelSchema,
82
- modelName,
83
- model
84
- ).catch((err) => {
85
- if (err instanceof Error) throw err;
86
- else return err;
87
- });
94
+ for (const modelSchema of schemas) {
95
+ const convertedSchemas = await this.__dereferenceAndConvert(
96
+ modelSchema,
97
+ modelName,
98
+ model
99
+ ).catch((err) => {
100
+ if (err instanceof Error) throw err;
101
+ else return err;
102
+ });
103
+
104
+ if (
105
+ typeof convertedSchemas.schemas === "object" &&
106
+ !Array.isArray(convertedSchemas.schemas) &&
107
+ convertedSchemas.schemas !== null
108
+ ) {
109
+ for (const [schemaName, schemaValue] of Object.entries(
110
+ convertedSchemas.schemas
111
+ )) {
112
+ if (schemaName === modelName) {
113
+ this.modelReferences[
114
+ schemaName
115
+ ] = `#/components/schemas/${modelName}`;
116
+ }
88
117
 
89
- if (
90
- typeof convertedSchemas.schemas === "object" &&
91
- !Array.isArray(convertedSchemas.schemas) &&
92
- convertedSchemas.schemas !== null
93
- ) {
94
- for (const [schemaName, schemaValue] of Object.entries(
95
- convertedSchemas.schemas
96
- )) {
97
- if (schemaName === modelName) {
98
- this.modelReferences[
99
- schemaName
100
- ] = `#/components/schemas/${modelName}`;
118
+ this.__addToComponents("schemas", schemaValue, schemaName);
101
119
  }
102
-
103
- this.__addToComponents("schemas", schemaValue, schemaName);
120
+ } else {
121
+ throw new Error(
122
+ `There was an error converting the ${
123
+ model.name
124
+ } schema. Model received looks like: \n\n${JSON.stringify(
125
+ model
126
+ )}. The convereted schema looks like \n\n${JSON.stringify(
127
+ convertedSchemas
128
+ )}`
129
+ );
104
130
  }
105
- } else {
106
- throw new Error(
107
- `There was an error converting the ${
108
- model.name
109
- } schema. Model received looks like: \n\n${JSON.stringify(
110
- model
111
- )}. The convereted schema looks like \n\n${JSON.stringify(
112
- convertedSchemas
113
- )}`
114
- );
115
131
  }
116
132
  }
117
133
  }
@@ -163,30 +179,18 @@ class SchemaHandler {
163
179
  }
164
180
  );
165
181
 
166
- if (this.shouldConvert) {
167
- this.logger.verbose(
168
- `dereferenced model: ${JSON.stringify(dereferencedSchema)}`
169
- );
170
-
171
- this.logger.verbose(`converting model: ${name}`);
172
- const convertedSchemas = SchemaConvertor.convert(
173
- dereferencedSchema,
174
- name
175
- );
182
+ this.logger.verbose(
183
+ `dereferenced model: ${JSON.stringify(dereferencedSchema)}`
184
+ );
176
185
 
177
- this.logger.verbose(
178
- `converted schemas: ${JSON.stringify(convertedSchemas)}`
179
- );
180
- return convertedSchemas;
181
- }
186
+ this.logger.verbose(`converting model: ${name}`);
187
+ const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, name);
182
188
 
183
189
  this.logger.verbose(
184
- `dereferenced model: ${JSON.stringify({
185
- schemas: { [name]: dereferencedSchema },
186
- })}`
190
+ `converted schemas: ${JSON.stringify(convertedSchemas)}`
187
191
  );
188
192
 
189
- return { schemas: { [name]: dereferencedSchema } };
193
+ return convertedSchemas;
190
194
  }
191
195
 
192
196
  async __dereferenceSchema(schema) {
package/test/.mocharc.js DELETED
@@ -1,9 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = {
4
- recursive: true,
5
- reporter: 'spec',
6
- spec: 'test/unit/*.spec.js',
7
- watch: false,
8
- 'watch-files': ['src/**/*.js', 'test/**/*.spec.js'],
9
- }
@@ -1,4 +0,0 @@
1
- {
2
- "struct": "error",
3
- "operation-2xx-response": "warn"
4
- }
@@ -1,5 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = {
4
- continueOnError: true, // Don't throw on the first error
5
- }
@@ -1,19 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = {
4
- processedInput: {
5
- options: {
6
- openApiVersion: '3.0.1'
7
- }
8
- },
9
- service: {
10
- service: 'myAPI',
11
- custom: {
12
- documentation: {
13
- title: 'My new API',
14
- description: 'This API does things',
15
- version: '0.0.1'
16
- }
17
- }
18
- }
19
- }