serverless-openapi-documenter 0.0.5 → 0.0.8

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
@@ -1,5 +1,14 @@
1
1
  # OpenAPI Generator for serverless
2
2
 
3
+ <p>
4
+ <a href="https://www.serverless.com">
5
+ <img src="http://public.serverless.com/badges/v3.svg">
6
+ </a>
7
+ <a href="https://www.npmjs.com/package/serverless-openapi-documenter">
8
+ <img src="https://img.shields.io/npm/v/serverless-openapi-documenter.svg?style=flat-square">
9
+ </a>
10
+ </p>
11
+
3
12
  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.
4
13
 
5
14
  Originally based off of: https://github.com/temando/serverless-openapi-documentation
@@ -47,13 +56,17 @@ Options:
47
56
  | info.version | custom.documentation.version OR random v4 uuid if not provided |
48
57
  | externalDocs.description | custom.documentation.externalDocumentation.description |
49
58
  | externalDocs.url | custom.documentation.externalDocumentation.url |
50
- | servers[].description | custom.documentation.servers.description |
51
- | servers[].url | custom.documentation.servers.url |
59
+ | servers[].description | custom.documentation.servers.description |
60
+ | servers[].url | custom.documentation.servers.url |
61
+ | tags[].name | custom.documentation.tags.name |
62
+ | tags[].description | custom.documentation.tags.description |
63
+ | tags[].externalDocs.url | custom.documentation.tags.externalDocumentation.url |
64
+ | tags[].externalDocs.description | custom.documentation.tags.externalDocumentation.description |
52
65
  | path[path] | functions.functions.events.[http OR httpApi].path |
53
66
  | path[path].summary | functions.functions.summary |
54
67
  | path[path].description | functions.functions.description |
55
- | path[path].servers[].description | functions.functions.servers.description |
56
- | path[path].servers[].url | functions.functions.servers.url |
68
+ | path[path].servers[].description | functions.functions.servers.description |
69
+ | path[path].servers[].url | functions.functions.servers.url |
57
70
  | path[path].[operation] | functions.functions.[http OR httpApi].method |
58
71
  | path[path].[operation].summary | functions.functions.[http OR httpApi].documentation.summary |
59
72
  | path[path].[operation].description | functions.functions.[http OR httpApi].documentation.description |
@@ -106,6 +119,12 @@ custom:
106
119
  servers:
107
120
  url: https://example.com
108
121
  description: The server
122
+ tags:
123
+ - name: tag1
124
+ description: this is a tag
125
+ externalDocumentation:
126
+ url: https://npmjs.com
127
+ description: A link to npm
109
128
  ```
110
129
 
111
130
  These configurations can be quite verbose; you can separate it out into it's own file, such as `serverless.doc.yml` as below:
@@ -134,7 +153,7 @@ The *required* directives for the models section are as follow:
134
153
  * `name`: the name of the schema
135
154
  * `description`: a description of the schema
136
155
  * `contentType`: the content type of the described request/response (ie. `application/json` or `application/xml`).
137
- * `schema`: The JSON Schema ([website](http://json-schema.org/)) that describes the model. You can either use inline `YAML` to define these, or refer to an external schema file as below
156
+ * `schema`: The JSON Schema ([website](http://json-schema.org/)) that describes the model. You can either use inline `YAML` to define these or use either an external file schema that serverless will resolve (as below), or a reference to an externally hosted schema that will be attempted to be resolved.
138
157
 
139
158
  ```yml
140
159
  custom:
@@ -195,6 +214,8 @@ functions:
195
214
  documentation:
196
215
  summary: "Create User"
197
216
  description: "Creates a user and then sends a generated password email"
217
+ tags:
218
+ - tag1
198
219
  externalDocumentation:
199
220
  url: https://bing.com
200
221
  description: A link to bing
@@ -241,7 +262,7 @@ Query parameters can be described as follow:
241
262
  * `name`: the name of the query variable
242
263
  * `description`: a description of the query variable
243
264
  * `required`: whether the query parameter is mandatory (boolean)
244
- * `schema`: JSON schema (inline or file)
265
+ * `schema`: JSON schema (inline, file or externally hosted)
245
266
 
246
267
  ```yml
247
268
  queryParams:
@@ -258,7 +279,7 @@ Path parameters can be described as follow:
258
279
 
259
280
  * `name`: the name of the query variable
260
281
  * `description`: a description of the query variable
261
- * `schema`: JSON schema (inline or file)
282
+ * `schema`: JSON schema (inline, file or externally hosted)
262
283
 
263
284
  ```yml
264
285
  pathParams:
@@ -275,7 +296,7 @@ Cookie parameters can be described as follow:
275
296
  * `name`: the name of the query variable
276
297
  * `description`: a description of the query variable
277
298
  * `required`: whether the query parameter is mandatory (boolean)
278
- * `schema`: JSON schema (inline or file)
299
+ * `schema`: JSON schema (inline, file or externally hosted)
279
300
 
280
301
  ```yml
281
302
  cookieParams:
@@ -333,7 +354,7 @@ The attributes for a header are as follow:
333
354
 
334
355
  * `name`: the name of the HTTP Header
335
356
  * `description`: a description of the HTTP Header
336
- * `schema`: JSON schema (inline or file)
357
+ * `schema`: JSON schema (inline, file or externally hosted)
337
358
 
338
359
  ```yml
339
360
  responseHeaders:
@@ -352,6 +373,82 @@ requestHeaders:
352
373
 
353
374
  Please view the example [serverless.yml](test/serverless\ 2/serverless.yml).
354
375
 
376
+ ## Notes on schemas
377
+
378
+ Schemas can be either: inline, in file or externally hosted. If they're inline or in file, the plugin will attempt to normalise the schema to [OpenAPI 3.0.X specification](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#schemaObject).
379
+
380
+ If they exist as an external reference, for instance:
381
+
382
+ ```yaml
383
+ schema: https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/bettercodehub.json
384
+ ```
385
+
386
+ We use the plugin [JSON Schema $Ref Parser](https://apitools.dev/json-schema-ref-parser/) to attempt to parse and resolve the references. There are limitations to this. Consider the schema:
387
+
388
+ ```json
389
+ {
390
+ "$schema": "https://json-schema.org/draft-04/schema",
391
+ "title": "Reusable Definitions",
392
+ "type": "object",
393
+ "id": "https://raw.githubusercontent.com/json-editor/json-editor/master/tests/fixtures/definitions.json",
394
+ "definitions": {
395
+ "address": {
396
+ "title": "Address",
397
+ "type": "object",
398
+ "properties": {
399
+ "street_address": { "type": "string" },
400
+ "city": { "type": "string" },
401
+ "state": { "type": "string" }
402
+ },
403
+ "required": ["street_address"]
404
+ },
405
+ "link" : {"$refs": "./properties.json#/properties/title"}
406
+ },
407
+ "properties": {
408
+ "address" : {"$refs": "#/definitions/address"}
409
+ }
410
+ }
411
+ ```
412
+ Where the definition "link" refers to a schema held in a directory that the resolver does not know about, we will not be able to fully resolve the schema which will likely cause errors in validation of the openAPI 3.0.X specification.
413
+
414
+ Because of the dependency we use to parse externally linked schemas, we can supply our own options to resolve schemas that are more difficult than a straight forward example.
415
+
416
+ You can create your own options file: https://apitools.dev/json-schema-ref-parser/docs/options.html to pass into the dependency that contains it's own resolver to allow you to resolve references that might be in hard to reach places. In your main project folder, you should have a folder called `options` with a file called `ref-parser.js` that looks like:
417
+
418
+ ```js
419
+ 'use strict'
420
+
421
+ // options from: https://apitools.dev/json-schema-ref-parser/docs/options.html
422
+
423
+ module.exports = {
424
+ continueOnError: true, // Don't throw on the first error
425
+ parse: {
426
+ json: false, // Disable the JSON parser
427
+ yaml: {
428
+ allowEmpty: false // Don't allow empty YAML files
429
+ },
430
+ text: {
431
+ canParse: [".txt", ".html"], // Parse .txt and .html files as plain text (strings)
432
+ encoding: 'utf16' // Use UTF-16 encoding
433
+ }
434
+ },
435
+ resolve: {
436
+ file: false, // Don't resolve local file references
437
+ http: {
438
+ timeout: 2000, // 2 second timeout
439
+ withCredentials: true, // Include auth credentials when resolving HTTP references
440
+ }
441
+ },
442
+ dereference: {
443
+ circular: false, // Don't allow circular $refs
444
+ excludedPathMatcher: (path) => // Skip dereferencing content under any 'example' key
445
+ path.includes("/example/")
446
+ }
447
+ }
448
+ ```
449
+
450
+ If you don't supply this file, it will use the default options.
451
+
355
452
  ## License
356
453
 
357
454
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-openapi-documenter",
3
- "version": "0.0.5",
3
+ "version": "0.0.8",
4
4
  "description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -28,12 +28,16 @@
28
28
  },
29
29
  "license": "MIT",
30
30
  "dependencies": {
31
+ "@apidevtools/json-schema-ref-parser": "^9.0.9",
31
32
  "chalk": "^4.1.2",
32
33
  "js-yaml": "^4.1.0",
33
- "json-schema-for-openapi": "^0.1.0",
34
+ "json-schema-for-openapi": "^0.1.2",
34
35
  "oas-validator": "^5.0.8",
35
36
  "openapi-to-postmanv2": "^3.2.0",
36
37
  "swagger2openapi": "^7.0.8",
37
38
  "uuid": "^8.3.2"
39
+ },
40
+ "engines": {
41
+ "node": ">=14"
38
42
  }
39
43
  }
@@ -1,8 +1,11 @@
1
1
  'use strict'
2
2
 
3
+ const path = require('path')
4
+
3
5
  const { v4: uuid } = require('uuid')
4
6
  const validator = require('oas-validator');
5
7
  const SchemaConvertor = require('json-schema-for-openapi')
8
+ const $RefParser = require("@apidevtools/json-schema-ref-parser");
6
9
 
7
10
  class DefinitionGenerator {
8
11
  constructor(serverless, options = {}) {
@@ -24,16 +27,35 @@ class DefinitionGenerator {
24
27
  }
25
28
 
26
29
  this.operationIds = []
30
+
31
+ try {
32
+ this.refParserOptions = require(path.resolve('options', 'ref-parser.js'))
33
+ } catch (err) {
34
+ this.refParserOptions = {}
35
+ }
36
+
27
37
  }
28
38
 
29
- parse() {
39
+ async parse() {
30
40
  this.createInfo()
31
- this.createPaths()
41
+ await this.createPaths()
42
+ .catch(err => {
43
+ throw err
44
+ })
45
+
32
46
  if (this.serverless.service.custom.documentation.servers) {
33
47
  const servers = this.createServers(this.serverless.service.custom.documentation.servers)
34
48
  Object.assign(this.openAPI, {servers: servers})
35
49
  }
36
- this.createExternalDocumentation()
50
+
51
+ if (this.serverless.service.custom.documentation.tags) {
52
+ this.createTags()
53
+ }
54
+
55
+ if (this.serverless.service.custom.documentation.externalDocumentation) {
56
+ const extDoc = this.createExternalDocumentation(this.serverless.service.custom.documentation.externalDocumentation)
57
+ Object.assign(this.openAPI, {externalDocs: extDoc})
58
+ }
37
59
  }
38
60
 
39
61
  createInfo() {
@@ -48,7 +70,7 @@ class DefinitionGenerator {
48
70
  Object.assign(this.openAPI, {info})
49
71
  }
50
72
 
51
- createPaths() {
73
+ async createPaths() {
52
74
  const paths = {}
53
75
  const httpFunctions = this.getHTTPFunctions()
54
76
 
@@ -65,7 +87,11 @@ class DefinitionGenerator {
65
87
  opId = `${httpFunction.functionInfo.name}-${uuid()}`
66
88
  }
67
89
 
68
- const path = this.createOperationObject(event.http.method || event.httpApi.method, documentation, opId)
90
+ const path = await this.createOperationObject(event.http.method || event.httpApi.method, documentation, opId)
91
+ .catch(err => {
92
+ throw err
93
+ })
94
+
69
95
  if (httpFunction.functionInfo?.summary)
70
96
  path.summary = httpFunction.functionInfo.summary
71
97
 
@@ -121,14 +147,35 @@ class DefinitionGenerator {
121
147
  return newServers
122
148
  }
123
149
 
124
- createExternalDocumentation() {
125
- const documentation = this.serverless.service.custom.documentation
126
- if (documentation.externalDocumentation) {
127
- Object.assign(this.openAPI, {externalDocs: {...documentation.externalDocumentation}})
150
+ createExternalDocumentation(docs) {
151
+ return {...docs}
152
+ // const documentation = this.serverless.service.custom.documentation
153
+ // if (documentation.externalDocumentation) {
154
+ // // Object.assign(this.openAPI, {externalDocs: {...documentation.externalDocumentation}})
155
+ // return
156
+ // }
157
+ }
158
+
159
+ createTags() {
160
+ const tags = []
161
+ for (const tag of this.serverless.service.custom.documentation.tags) {
162
+ const obj = {
163
+ name: tag.name,
164
+ }
165
+
166
+ if (tag.description) {
167
+ obj.description = tag.description
168
+ }
169
+
170
+ if (tag.externalDocumentation) {
171
+ obj.externalDocs = this.createExternalDocumentation(tag.externalDocumentation)
172
+ }
173
+ tags.push(obj)
128
174
  }
175
+ Object.assign(this.openAPI, {tags: tags})
129
176
  }
130
177
 
131
- createOperationObject(method, documentation, name = uuid()) {
178
+ async createOperationObject(method, documentation, name = uuid()) {
132
179
  const obj = {
133
180
  summary: documentation.summary || '',
134
181
  description: documentation.description || '',
@@ -138,22 +185,34 @@ class DefinitionGenerator {
138
185
  }
139
186
 
140
187
  if (documentation.pathParams) {
141
- const paramObject = this.createParamObject('path', documentation)
188
+ const paramObject = await this.createParamObject('path', documentation)
189
+ .catch(err => {
190
+ throw err
191
+ })
142
192
  obj.parameters = obj.parameters.concat(paramObject)
143
193
  }
144
194
 
145
195
  if (documentation.queryParams) {
146
- const paramObject = this.createParamObject('query', documentation)
196
+ const paramObject = await this.createParamObject('query', documentation)
197
+ .catch(err => {
198
+ throw err
199
+ })
147
200
  obj.parameters = obj.parameters.concat(paramObject)
148
201
  }
149
202
 
150
203
  if (documentation.headerParams) {
151
- const paramObject = this.createParamObject('header', documentation)
204
+ const paramObject = await this.createParamObject('header', documentation)
205
+ .catch(err => {
206
+ throw err
207
+ })
152
208
  obj.parameters = obj.parameters.concat(paramObject)
153
209
  }
154
210
 
155
211
  if (documentation.cookieParams) {
156
- const paramObject = this.createParamObject('cookie', documentation)
212
+ const paramObject = await this.createParamObject('cookie', documentation)
213
+ .catch(err => {
214
+ throw err
215
+ })
157
216
  obj.parameters = obj.parameters.concat(paramObject)
158
217
  }
159
218
 
@@ -165,10 +224,16 @@ class DefinitionGenerator {
165
224
  obj.deprecated = documentation.deprecated
166
225
 
167
226
  if (documentation.requestBody)
168
- obj.requestBody = this.createRequestBody(documentation)
227
+ obj.requestBody = await this.createRequestBody(documentation)
228
+ .catch(err => {
229
+ throw err
230
+ })
169
231
 
170
232
  if (documentation.methodResponses)
171
- obj.responses = this.createResponses(documentation)
233
+ obj.responses = await this.createResponses(documentation)
234
+ .catch(err => {
235
+ throw err
236
+ })
172
237
 
173
238
  if (documentation.servers) {
174
239
  const servers = this.createServers(documentation.servers)
@@ -178,14 +243,17 @@ class DefinitionGenerator {
178
243
  return {[method]: obj}
179
244
  }
180
245
 
181
- createResponses(documentation) {
246
+ async createResponses(documentation) {
182
247
  const responses = {}
183
248
  for (const response of documentation.methodResponses) {
184
249
  const obj = {
185
250
  description: response.responseBody.description || '',
186
251
  }
187
252
 
188
- obj.content = this.createMediaTypeObject(response.responseModels, 'responses')
253
+ obj.content = await this.createMediaTypeObject(response.responseModels, 'responses')
254
+ .catch(err => {
255
+ throw err
256
+ })
189
257
 
190
258
  Object.assign(responses,{[response.statusCode]: obj})
191
259
  }
@@ -193,18 +261,21 @@ class DefinitionGenerator {
193
261
  return responses
194
262
  }
195
263
 
196
- createRequestBody(documentation) {
264
+ async createRequestBody(documentation) {
197
265
  const obj = {
198
266
  description: documentation.requestBody.description,
199
267
  required: documentation.requestBody.required || false,
200
268
  }
201
269
 
202
- obj.content = this.createMediaTypeObject(documentation.requestModels, 'requestBody')
270
+ obj.content = await this.createMediaTypeObject(documentation.requestModels, 'requestBody')
271
+ .catch(err => {
272
+ throw err
273
+ })
203
274
 
204
275
  return obj
205
276
  }
206
277
 
207
- createMediaTypeObject(models, type) {
278
+ async createMediaTypeObject(models, type) {
208
279
  const mediaTypeObj = {}
209
280
  for (const mediaTypeDocumentation of this.serverless.service.custom.documentation.models) {
210
281
  if (Object.values(models).includes(mediaTypeDocumentation.name)) {
@@ -222,59 +293,12 @@ class DefinitionGenerator {
222
293
  obj.examples = this.createExamples(mediaTypeDocumentation.examples)
223
294
 
224
295
  if (mediaTypeDocumentation.content[contentKey].schema) {
225
- const schema = SchemaConvertor.convert(mediaTypeDocumentation.content[contentKey].schema)
226
- for (const key of Object.keys(schema.schemas)) {
227
- if (key === 'main' || key.split('-')[0] === 'main') {
228
- obj.schema = {
229
- $ref: `#/components/schemas/${mediaTypeDocumentation.name}`
230
- }
231
-
232
- if (this.openAPI?.components) {
233
- if (this.openAPI.components?.schemas) {
234
- const schemaObj = {
235
- [mediaTypeDocumentation.name]: schema.schemas[key]
236
- }
237
- Object.assign(this.openAPI.components.schemas, schemaObj)
238
- } else {
239
- const schemaObj = {
240
- [mediaTypeDocumentation.name]: schema.schemas[key]
241
- }
242
- Object.assign(this.openAPI.components, {schemas: schemaObj})
243
- }
244
- } else {
245
- const components = {
246
- components: {
247
- schemas: {
248
- [mediaTypeDocumentation.name]: schema.schemas[key]
249
- }
250
- }
251
- }
252
- Object.assign(this.openAPI, components)
253
- }
254
- } else {
255
- if (this.openAPI?.components) {
256
- if (this.openAPI.components?.schemas) {
257
- const schemaObj = {
258
- [key]: schema.schemas[key]
259
- }
260
- Object.assign(this.openAPI.components.schemas, schemaObj)
261
- } else {
262
- const schemaObj = {
263
- [key]: schema.schemas[key]
264
- }
265
- Object.assign(this.openAPI.components, {schemas: schemaObj})
266
- }
267
- } else {
268
- const components = {
269
- components: {
270
- schemas: {
271
- [key]: schema.schemas[key]
272
- }
273
- }
274
- }
275
- Object.assign(this.openAPI, components)
276
- }
277
- }
296
+ const schemaRef = await this.schemaCreator(mediaTypeDocumentation.content[contentKey].schema, mediaTypeDocumentation.name)
297
+ .catch(err => {
298
+ throw err
299
+ })
300
+ obj.schema = {
301
+ $ref: schemaRef
278
302
  }
279
303
  }
280
304
 
@@ -284,7 +308,7 @@ class DefinitionGenerator {
284
308
  return mediaTypeObj
285
309
  }
286
310
 
287
- createParamObject(paramIn, documentation) {
311
+ async createParamObject(paramIn, documentation) {
288
312
  const params = []
289
313
  for (const param of documentation[`${paramIn}Params`]) {
290
314
  const obj = {
@@ -305,7 +329,7 @@ class DefinitionGenerator {
305
329
  if (param.style)
306
330
  obj.style = param.style
307
331
 
308
- if (param.explode)
332
+ if (Object.keys(param).includes('explode'))
309
333
  obj.explode = param.explode
310
334
 
311
335
  if (paramIn === 'query' && param.allowReserved)
@@ -318,10 +342,12 @@ class DefinitionGenerator {
318
342
  obj.examples = this.createExamples(param.examples)
319
343
 
320
344
  if (param.schema) {
321
- const schema = SchemaConvertor.convert(param.schema)
322
- if (schema.schemas.main) {
323
- Object.assign(obj,{schema: schema.schemas.main})
324
-
345
+ const schemaRef = await this.schemaCreator(param.schema, param.name)
346
+ .catch(err => {
347
+ throw err
348
+ })
349
+ obj.schema = {
350
+ $ref: schemaRef
325
351
  }
326
352
  }
327
353
 
@@ -330,6 +356,55 @@ class DefinitionGenerator {
330
356
  return params;
331
357
  }
332
358
 
359
+ async schemaCreator(schema, name) {
360
+ const addToComponents = (schema, name) => {
361
+ const schemaObj = {
362
+ [name]: schema
363
+ }
364
+
365
+ if (this.openAPI?.components) {
366
+ if (this.openAPI.components?.schemas) {
367
+ Object.assign(this.openAPI.components.schemas, schemaObj)
368
+ } else {
369
+ Object.assign(this.openAPI.components, {schemas: schemaObj})
370
+ }
371
+ } else {
372
+ const components = {
373
+ components: {
374
+ schemas: schemaObj
375
+ }
376
+ }
377
+
378
+ Object.assign(this.openAPI, components)
379
+ }
380
+ }
381
+
382
+ if (typeof schema !== 'string' && Object.keys(schema).length > 0) {
383
+ const convertedSchema = SchemaConvertor.convert(schema)
384
+ for (const key of Object.keys(convertedSchema.schemas)) {
385
+ if (key === 'main' || key.split('-')[0] === 'main') {
386
+ const ref = `#/components/schemas/${name}`
387
+
388
+ addToComponents(convertedSchema.schemas[key], name)
389
+ return ref
390
+ } else {
391
+ addToComponents(convertedSchema.schemas[key], key)
392
+ }
393
+ }
394
+ } else {
395
+ const combinedSchema = await $RefParser.dereference(schema, this.refParserOptions)
396
+ .catch(err => {
397
+ console.error(err)
398
+ throw err
399
+ })
400
+
401
+ return await this.schemaCreator(combinedSchema, name)
402
+ .catch(err => {
403
+ throw err
404
+ })
405
+ }
406
+ }
407
+
333
408
  createExamples(examples) {
334
409
  const examplesObj = {}
335
410
 
@@ -67,11 +67,18 @@ class OpenAPIGenerator {
67
67
  required: ['documentation'],
68
68
  });
69
69
 
70
+ this.serverless.configSchemaHandler.defineFunctionEventProperties('aws', 'httpApi', {
71
+ properties: {
72
+ documentation: { type: 'object' },
73
+ },
74
+ required: ['documentation'],
75
+ });
76
+
70
77
  this.serverless.configSchemaHandler.defineFunctionProperties('aws', {
71
78
  properties: {
72
79
  // description: {type: 'string'},
73
80
  summary: {type: 'string'},
74
- servers: {type: ['object', 'array']},
81
+ servers: {anyOf: [{type:'object'}, {type:'array'}]},
75
82
  }
76
83
  })
77
84
  }
@@ -97,12 +104,15 @@ class OpenAPIGenerator {
97
104
  const config = this.processCliInput()
98
105
  const generator = new DefinitionGenerator(this.serverless);
99
106
 
100
- generator.parse();
107
+ await generator.parse()
108
+ .catch(err => {
109
+ this.log('error', chalk.bold.red(`ERROR: An error was thrown generating the OpenAPI v3 documentation`))
110
+ throw new this.serverless.classes.Error(err)
111
+ })
101
112
 
102
113
  const valid = await generator.validate()
103
114
  .catch(err => {
104
-
105
- this.log('error', chalk.bold.red(`ERROR: An error was thrown generation the OpenAPI v3 documentation`))
115
+ this.log('error', chalk.bold.red(`ERROR: An error was thrown validating the OpenAPI v3 documentation`))
106
116
  throw new this.serverless.classes.Error(err)
107
117
  })
108
118
 
@@ -19,6 +19,8 @@ functions:
19
19
  documentation:
20
20
  summary: Create User
21
21
  description: Creates a user and then sends a generated password email
22
+ tags:
23
+ - jesus
22
24
  externalDocumentation:
23
25
  url: https://bing.com
24
26
  description: A link to bing
@@ -67,6 +69,12 @@ custom:
67
69
  documentation:
68
70
  description: This is a description of what this does
69
71
  version: 1.0.0
72
+ tags:
73
+ - name: jesus
74
+ description: jesus was a man
75
+ externalDocumentation:
76
+ url: https://whitehouse.gov
77
+ description: a link to the whitehouse
70
78
  externalDocumentation:
71
79
  url: https://google.com
72
80
  description: A link to google