serverless-openapi-documenter 0.0.1
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/.github/workflows/npm-publish.yml +33 -0
- package/LICENSE +21 -0
- package/README.md +337 -0
- package/index.js +3 -0
- package/package.json +23 -0
- package/src/definitionGenerator.js +304 -0
- package/src/openAPIGenerator.js +210 -0
- package/test/models/ErrorResponse.json +118 -0
- package/test/models/PutDocumentResponse.json +5 -0
- package/test/serverless 1/package-lock.json +15981 -0
- package/test/serverless 1/package.json +14 -0
- package/test/serverless 1/serverless.docs.yml +62 -0
- package/test/serverless 1/serverless.yml +79 -0
- package/test/serverless 2/index.js +220 -0
- package/test/serverless 2/package-lock.json +12898 -0
- package/test/serverless 2/package.json +14 -0
- package/test/serverless 2/serverless.docs.yml +62 -0
- package/test/serverless 2/serverless.yml +91 -0
- package/test/serverless 3/serverless.docs.yml +62 -0
- package/test/serverless 3/serverless.yml +78 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { v4: uuid } = require('uuid')
|
|
4
|
+
const validator = require('oas-validator');
|
|
5
|
+
const SchemaConvertor = require('json-schema-for-openapi')
|
|
6
|
+
|
|
7
|
+
class DefinitionGenerator {
|
|
8
|
+
constructor(serverless, options = {}) {
|
|
9
|
+
this.version = options.v || '3.0.0'
|
|
10
|
+
|
|
11
|
+
this.serverless = serverless
|
|
12
|
+
this.httpKeys = {
|
|
13
|
+
http: 'http',
|
|
14
|
+
httpAPI: 'httpApi',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.componentsSchemas = {
|
|
18
|
+
requestBody: 'requestBodies',
|
|
19
|
+
responses: 'responses',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.openAPI = {
|
|
23
|
+
openapi: this.version,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
parse() {
|
|
28
|
+
this.createInfo()
|
|
29
|
+
this.createPaths()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
createInfo() {
|
|
33
|
+
const service = this.serverless.service
|
|
34
|
+
const documentation = this.serverless.service.custom.documentation;
|
|
35
|
+
|
|
36
|
+
const info = {
|
|
37
|
+
title: service.service,
|
|
38
|
+
description: documentation?.description || '',
|
|
39
|
+
version: documentation?.version || uuid(),
|
|
40
|
+
}
|
|
41
|
+
Object.assign(this.openAPI, {info})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
createPaths() {
|
|
45
|
+
const paths = {}
|
|
46
|
+
const httpFunctions = this.getHTTPFunctions()
|
|
47
|
+
|
|
48
|
+
for (const httpFunction of httpFunctions) {
|
|
49
|
+
for (const event of httpFunction.event) {
|
|
50
|
+
if (event?.http?.documentation || event?.httpApi?.documentation) {
|
|
51
|
+
const documentation = event.http.documentation || event.httpApi.documentation
|
|
52
|
+
|
|
53
|
+
const path = this.createOperationObject(event.http.method || event.httpApi.method, documentation, httpFunction.functionInfo.name)
|
|
54
|
+
if (httpFunction.functionInfo?.summary)
|
|
55
|
+
path.summary = httpFunction.functionInfo.summary
|
|
56
|
+
|
|
57
|
+
if (httpFunction.functionInfo?.description)
|
|
58
|
+
path.description = httpFunction.functionInfo.description
|
|
59
|
+
|
|
60
|
+
Object.assign(paths, {[`/${event.http.path}`]: path})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
Object.assign(this.openAPI, {paths})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
createOperationObject(method, documentation, name = uuid()) {
|
|
68
|
+
const obj = {
|
|
69
|
+
summary: documentation.summary || '',
|
|
70
|
+
description: documentation.description || '',
|
|
71
|
+
operationId: documentation.operationId || name,
|
|
72
|
+
parameters: [],
|
|
73
|
+
tags: documentation.tags || []
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (documentation.pathParams) {
|
|
77
|
+
const paramObject = this.createParamObject('path', documentation)
|
|
78
|
+
obj.parameters = obj.parameters.concat(paramObject)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (documentation.queryParams) {
|
|
82
|
+
const paramObject = this.createParamObject('query', documentation)
|
|
83
|
+
obj.parameters = obj.parameters.concat(paramObject)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (documentation.headerParams) {
|
|
87
|
+
const paramObject = this.createParamObject('header', documentation)
|
|
88
|
+
obj.parameters = obj.parameters.concat(paramObject)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (documentation.cookieParams) {
|
|
92
|
+
const paramObject = this.createParamObject('cookie', documentation)
|
|
93
|
+
obj.parameters = obj.parameters.concat(paramObject)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (Object.keys(documentation).includes('deprecated'))
|
|
97
|
+
obj[method].deprecated = documentation.deprecated
|
|
98
|
+
|
|
99
|
+
if (documentation.requestBody)
|
|
100
|
+
obj.requestBody = this.createRequestBody(documentation)
|
|
101
|
+
|
|
102
|
+
if (documentation.methodResponses)
|
|
103
|
+
obj.responses = this.createResponses(documentation)
|
|
104
|
+
|
|
105
|
+
return {[method]: obj}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
createResponses(documentation) {
|
|
109
|
+
const responses = {}
|
|
110
|
+
for (const response of documentation.methodResponses) {
|
|
111
|
+
const obj = {
|
|
112
|
+
description: response.responseBody.description || '',
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
obj.content = this.createMediaTypeObject(response.responseModels, 'responses')
|
|
116
|
+
|
|
117
|
+
Object.assign(responses,{[response.statusCode]: obj})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return responses
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
createRequestBody(documentation) {
|
|
124
|
+
const obj = {
|
|
125
|
+
description: documentation.requestBody.description,
|
|
126
|
+
required: documentation.requestBody.required || false,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
obj.content = this.createMediaTypeObject(documentation.requestModels, 'requestBody')
|
|
130
|
+
|
|
131
|
+
return obj
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
createMediaTypeObject(models, type) {
|
|
135
|
+
const mediaTypeObj = {}
|
|
136
|
+
for (const mediaTypeDocumentation of this.serverless.service.custom.documentation.models) {
|
|
137
|
+
if (Object.values(models).includes(mediaTypeDocumentation.name)) {
|
|
138
|
+
let contentKey = ''
|
|
139
|
+
for (const [key, value] of Object.entries(models)) {
|
|
140
|
+
if (value === mediaTypeDocumentation.name)
|
|
141
|
+
contentKey = key;
|
|
142
|
+
}
|
|
143
|
+
const obj = {}
|
|
144
|
+
|
|
145
|
+
if (mediaTypeDocumentation.example)
|
|
146
|
+
obj.example = mediaTypeDocumentation.example
|
|
147
|
+
|
|
148
|
+
if (mediaTypeDocumentation.examples)
|
|
149
|
+
obj.examples = this.createExamples(mediaTypeDocumentation.examples)
|
|
150
|
+
|
|
151
|
+
if (mediaTypeDocumentation.content[contentKey].schema) {
|
|
152
|
+
const schema = SchemaConvertor.convert(mediaTypeDocumentation.content[contentKey].schema)
|
|
153
|
+
for (const key of Object.keys(schema.schemas)) {
|
|
154
|
+
if (key === 'main' || key.split('-')[0] === 'main') {
|
|
155
|
+
obj.schema = {
|
|
156
|
+
$ref: `#/components/schemas/${mediaTypeDocumentation.name}`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (this.openAPI?.components) {
|
|
160
|
+
if (this.openAPI.components?.schemas) {
|
|
161
|
+
const schemaObj = {
|
|
162
|
+
[mediaTypeDocumentation.name]: schema.schemas[key]
|
|
163
|
+
}
|
|
164
|
+
Object.assign(this.openAPI.components.schemas, schemaObj)
|
|
165
|
+
} else {
|
|
166
|
+
const schemaObj = {
|
|
167
|
+
[mediaTypeDocumentation.name]: schema.schemas[key]
|
|
168
|
+
}
|
|
169
|
+
Object.assign(this.openAPI.components, {schemas: schemaObj})
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
const components = {
|
|
173
|
+
components: {
|
|
174
|
+
schemas: {
|
|
175
|
+
[mediaTypeDocumentation.name]: schema.schemas[key]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
Object.assign(this.openAPI, components)
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
if (this.openAPI?.components) {
|
|
183
|
+
if (this.openAPI.components?.schemas) {
|
|
184
|
+
const schemaObj = {
|
|
185
|
+
[key]: schema.schemas[key]
|
|
186
|
+
}
|
|
187
|
+
Object.assign(this.openAPI.components.schemas, schemaObj)
|
|
188
|
+
} else {
|
|
189
|
+
const schemaObj = {
|
|
190
|
+
[key]: schema.schemas[key]
|
|
191
|
+
}
|
|
192
|
+
Object.assign(this.openAPI.components, {schemas: schemaObj})
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
const components = {
|
|
196
|
+
components: {
|
|
197
|
+
schemas: {
|
|
198
|
+
[key]: schema.schemas[key]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
Object.assign(this.openAPI, components)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
Object.assign(mediaTypeObj, {[contentKey]: obj})
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return mediaTypeObj
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
createParamObject(paramIn, documentation) {
|
|
215
|
+
const params = []
|
|
216
|
+
for (const param of documentation[`${paramIn}Params`]) {
|
|
217
|
+
const obj = {
|
|
218
|
+
name: param.name,
|
|
219
|
+
in: paramIn,
|
|
220
|
+
description: param.description || '',
|
|
221
|
+
required: (paramIn === 'path') ? true : param.required || false,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (Object.keys(param).includes('deprecated')) {
|
|
225
|
+
obj.deprecated = param.deprecated
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (paramIn === 'query' && Object.keys(param).includes('allowEmptyValue')) {
|
|
229
|
+
obj.allowEmptyValue = param.allowEmptyValue
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (param.style)
|
|
233
|
+
obj.style = param.style
|
|
234
|
+
|
|
235
|
+
if (param.explode)
|
|
236
|
+
obj.explode = param.explode
|
|
237
|
+
|
|
238
|
+
if (paramIn === 'query' && param.allowReserved)
|
|
239
|
+
obj.allowReserved = param.allowReserved
|
|
240
|
+
|
|
241
|
+
if (param.example)
|
|
242
|
+
obj.example = param.example
|
|
243
|
+
|
|
244
|
+
if (param.examples)
|
|
245
|
+
obj.examples = this.createExamples(param.examples)
|
|
246
|
+
|
|
247
|
+
if (param.schema) {
|
|
248
|
+
const schema = SchemaConvertor.convert(param.schema)
|
|
249
|
+
if (schema.schemas.main) {
|
|
250
|
+
Object.assign(obj,{schema: schema.schemas.main})
|
|
251
|
+
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
params.push(obj)
|
|
256
|
+
}
|
|
257
|
+
return params;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
createExamples(examples) {
|
|
261
|
+
const examplesObj = {}
|
|
262
|
+
|
|
263
|
+
for(const example of examples) {
|
|
264
|
+
Object.assign(examplesObj, {[example.name]: example})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return examplesObj;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
getHTTPFunctions() {
|
|
271
|
+
const isHttpFunction = (funcType) => {
|
|
272
|
+
const keys = Object.keys(funcType)
|
|
273
|
+
if (keys.includes(this.httpKeys.http) || keys.includes(this.httpKeys.httpAPI))
|
|
274
|
+
return true
|
|
275
|
+
}
|
|
276
|
+
const functionNames = this.serverless.service.getAllFunctions()
|
|
277
|
+
|
|
278
|
+
return functionNames.map(functionName => {
|
|
279
|
+
return this.serverless.service.getFunction(functionName)
|
|
280
|
+
})
|
|
281
|
+
.filter(functionType => {
|
|
282
|
+
if (functionType?.events.some(isHttpFunction))
|
|
283
|
+
return functionType
|
|
284
|
+
})
|
|
285
|
+
.map(functionType => {
|
|
286
|
+
const event = functionType.events.filter(isHttpFunction)
|
|
287
|
+
return {
|
|
288
|
+
functionInfo: functionType,
|
|
289
|
+
handler: functionType.handler,
|
|
290
|
+
name: functionType.name,
|
|
291
|
+
event
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async validate() {
|
|
297
|
+
return await validator.validateInner(this.openAPI, {})
|
|
298
|
+
.catch(err => {
|
|
299
|
+
throw err
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
module.exports = DefinitionGenerator
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const yaml = require('js-yaml');
|
|
5
|
+
const chalk = require('chalk')
|
|
6
|
+
|
|
7
|
+
const DefinitionGenerator = require('./definitionGenerator')
|
|
8
|
+
const PostmanGenerator = require('openapi-to-postmanv2')
|
|
9
|
+
|
|
10
|
+
class OpenAPIGenerator {
|
|
11
|
+
constructor(serverless, options, {log = {}} = {}) {
|
|
12
|
+
this.logOutput = log;
|
|
13
|
+
this.serverless = serverless
|
|
14
|
+
this.options = options
|
|
15
|
+
this.defaultLog = 'notice';
|
|
16
|
+
this.commands = {
|
|
17
|
+
openapi: {
|
|
18
|
+
commands: {
|
|
19
|
+
generate: {
|
|
20
|
+
lifecycleEvents: [
|
|
21
|
+
'serverless',
|
|
22
|
+
],
|
|
23
|
+
usage: 'Generate OpenAPI v3 Documentation',
|
|
24
|
+
options: {
|
|
25
|
+
output: {
|
|
26
|
+
usage: 'Output file location [default: openapi.json|yml]',
|
|
27
|
+
shortcut: 'o',
|
|
28
|
+
type: 'string',
|
|
29
|
+
},
|
|
30
|
+
format: {
|
|
31
|
+
usage: 'OpenAPI file format (yml|json) [default: json]',
|
|
32
|
+
shortcut: 'f',
|
|
33
|
+
type: 'string',
|
|
34
|
+
},
|
|
35
|
+
indent: {
|
|
36
|
+
usage: 'File indentation in spaces [default: 2]',
|
|
37
|
+
shortcut: 'i',
|
|
38
|
+
type: 'string',
|
|
39
|
+
},
|
|
40
|
+
openApiVersion: {
|
|
41
|
+
usage: 'OpenAPI version number [default 3.0.0]',
|
|
42
|
+
shortcut: 'a',
|
|
43
|
+
type: 'string'
|
|
44
|
+
},
|
|
45
|
+
postmanCollection: {
|
|
46
|
+
usage: 'Output a postman collection and attach to openApi external documents [default: postman.json if passed]',
|
|
47
|
+
shortcut: 'p',
|
|
48
|
+
type: 'string'
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.hooks = {
|
|
57
|
+
// 'before:deploy': this.beforeDeploy.bind(this),
|
|
58
|
+
'openapi:generate:serverless': this.generate.bind(this),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
this.customVars = this.serverless.variables.service.custom;
|
|
62
|
+
|
|
63
|
+
this.serverless.configSchemaHandler.defineFunctionEventProperties('aws', 'http', {
|
|
64
|
+
properties: {
|
|
65
|
+
documentation: { type: 'object' },
|
|
66
|
+
},
|
|
67
|
+
required: ['documentation'],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.serverless.configSchemaHandler.defineFunctionProperties('aws', {
|
|
71
|
+
properties: {
|
|
72
|
+
// description: {type: 'string'},
|
|
73
|
+
summary: {type: 'string'}
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
log(type = this.defaultLog, ...str) {
|
|
79
|
+
switch(this.serverless.version[0]) {
|
|
80
|
+
case '2':
|
|
81
|
+
this.serverless.cli.log(str)
|
|
82
|
+
break
|
|
83
|
+
|
|
84
|
+
case '3':
|
|
85
|
+
this.logOutput[type](str)
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
default:
|
|
89
|
+
process.stdout.write(str.join(' '))
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async generate() {
|
|
95
|
+
this.log(this.defaultLog, chalk.bold.underline('OpenAPI v3 Document Generation'))
|
|
96
|
+
const config = this.processCliInput()
|
|
97
|
+
const generator = new DefinitionGenerator(this.serverless);
|
|
98
|
+
|
|
99
|
+
generator.parse();
|
|
100
|
+
|
|
101
|
+
const valid = await generator.validate()
|
|
102
|
+
.catch(err => {
|
|
103
|
+
|
|
104
|
+
this.log('error', chalk.bold.red(`ERROR: An error was thrown generation the OpenAPI v3 documentation`))
|
|
105
|
+
throw new this.serverless.classes.Error(err)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
if (valid)
|
|
109
|
+
this.log(this.defaultLog, chalk.bold.green('OpenAPI v3 Documentation Successfully Generated'))
|
|
110
|
+
|
|
111
|
+
if (config.postmanCollection) {
|
|
112
|
+
const postmanGeneration = (err, result) => {
|
|
113
|
+
if (err) {
|
|
114
|
+
this.log('error', chalk.bold.red(`ERROR: An error was thrown when generating the postman collection`))
|
|
115
|
+
throw new this.serverless.classes.Error(err)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.log(this.defaultLog, chalk.bold.green('postman collection v2 Documentation Successfully Generated'))
|
|
119
|
+
try {
|
|
120
|
+
fs.writeFileSync(config.postmanCollection, JSON.stringify(result.output[0].data))
|
|
121
|
+
this.log(this.defaultLog, chalk.bold.green('postman collection v2 Documentation Successfully Written'))
|
|
122
|
+
} catch (err) {
|
|
123
|
+
this.log('error', chalk.bold.red(`ERROR: An error was thrown whilst writing the postman collection`))
|
|
124
|
+
throw new this.serverless.classes.Error(err)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const postmanCollection = PostmanGenerator.convert(
|
|
129
|
+
{type: 'json', data: generator.openAPI},
|
|
130
|
+
{},
|
|
131
|
+
postmanGeneration
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let output
|
|
136
|
+
switch (config.format.toLowerCase()) {
|
|
137
|
+
case 'json':
|
|
138
|
+
output = JSON.stringify(generator.openAPI, null, config.indent);
|
|
139
|
+
break;
|
|
140
|
+
case 'yaml':
|
|
141
|
+
default:
|
|
142
|
+
output = yaml.dump(generator.openAPI, { indent: config.indent });
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
fs.writeFileSync(config.file, output);
|
|
147
|
+
this.log(this.defaultLog, chalk.bold.green('OpenAPI v3 Documentation Successfully Written'))
|
|
148
|
+
} catch (err) {
|
|
149
|
+
this.log('error', chalk.bold.red(`ERROR: An error was thrown whilst writing the openAPI Documentation`))
|
|
150
|
+
throw new this.serverless.classes.Error(err)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
processCliInput () {
|
|
155
|
+
const config = {
|
|
156
|
+
format: 'json',
|
|
157
|
+
file: 'openapi.json',
|
|
158
|
+
indent: 2,
|
|
159
|
+
openApiVersion: '3.0.0',
|
|
160
|
+
postmanCollection: 'postman.json'
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
config.indent = this.serverless.processedInput.options.indent || 2;
|
|
164
|
+
config.format = this.serverless.processedInput.options.format || 'json';
|
|
165
|
+
config.openApiVersion = this.serverless.processedInput.options.openApiVersion || '3.0.0';
|
|
166
|
+
config.postmanCollection = this.serverless.processedInput.options.postmanCollection || null
|
|
167
|
+
|
|
168
|
+
if (['yaml', 'json'].indexOf(config.format.toLowerCase()) < 0) {
|
|
169
|
+
throw new Error('Invalid Output Format Specified - must be one of "yaml" or "json"');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
config.file = this.serverless.processedInput.options.output ||
|
|
173
|
+
((config.format === 'yaml') ? 'openapi.yml' : 'openapi.json');
|
|
174
|
+
|
|
175
|
+
this.log(
|
|
176
|
+
this.defaultLog,
|
|
177
|
+
`${chalk.bold.green('[OPTIONS]')}`,
|
|
178
|
+
` openApiVersion: "${chalk.bold.red(String(config.openApiVersion))}"`,
|
|
179
|
+
` format: "${chalk.bold.red(config.format)}"`,
|
|
180
|
+
` output file: "${chalk.bold.red(config.file)}"`,
|
|
181
|
+
` indentation: "${chalk.bold.red(String(config.indent))}"`,
|
|
182
|
+
` ${config.postmanCollection ? `postman collection: ${chalk.bold.red(config.postmanCollection)}`: `\n\n`}`
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return config
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
validateDetails(validation) {
|
|
189
|
+
if (validation.valid) {
|
|
190
|
+
this.log(this.defaultLog, `${ chalk.bold.green('[VALIDATION]') } OpenAPI valid: ${chalk.bold.green('true')}\n\n`);
|
|
191
|
+
} else {
|
|
192
|
+
this.log(this.defaultLog, `${chalk.bold.red('[VALIDATION]')} Failed to validate OpenAPI document: \n\n`);
|
|
193
|
+
this.log(this.defaultLog, `${chalk.bold.green('Context:')} ${JSON.stringify(validation.context, null, 2)}\n`);
|
|
194
|
+
this.log(this.defaultLog, `${chalk.bold.green('Error Message:')} ${JSON.stringify(validation.error, null, 2)}\n`);
|
|
195
|
+
if (typeof validation.error === 'string') {
|
|
196
|
+
this.log(this.defaultLog, `${validation.error}\n\n`);
|
|
197
|
+
} else {
|
|
198
|
+
for (const info of validation.error) {
|
|
199
|
+
this.log(this.defaultLog, chalk.grey('\n\n--------\n\n'));
|
|
200
|
+
this.log(this.defaultLog, ' ', chalk.blue(info.dataPath), '\n');
|
|
201
|
+
this.log(this.defaultLog, ' ', info.schemaPath, chalk.bold.yellow(info.message));
|
|
202
|
+
this.log(this.defaultLog, chalk.grey('\n\n--------\n\n'));
|
|
203
|
+
this.log(this.defaultLog, `${inspect(info, { colors: true, depth: 2 })}\n\n`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = OpenAPIGenerator
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
3
|
+
"title": "JSON API Schema",
|
|
4
|
+
"description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": [
|
|
7
|
+
"errors"
|
|
8
|
+
],
|
|
9
|
+
"properties": {
|
|
10
|
+
"errors": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": {
|
|
13
|
+
"$ref": "#/definitions/error"
|
|
14
|
+
},
|
|
15
|
+
"uniqueItems": true
|
|
16
|
+
},
|
|
17
|
+
"meta": {
|
|
18
|
+
"$ref": "#/definitions/meta"
|
|
19
|
+
},
|
|
20
|
+
"links": {
|
|
21
|
+
"$ref": "#/definitions/links"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"definitions": {
|
|
26
|
+
"meta": {
|
|
27
|
+
"description": "Non-standard meta-information that can not be represented as an attribute or relationship.",
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": true
|
|
30
|
+
},
|
|
31
|
+
"links": {
|
|
32
|
+
"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.",
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"self": {
|
|
36
|
+
"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.",
|
|
37
|
+
"type": "string",
|
|
38
|
+
"format": "uri"
|
|
39
|
+
},
|
|
40
|
+
"related": {
|
|
41
|
+
"$ref": "#/definitions/link"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"additionalProperties": true
|
|
45
|
+
},
|
|
46
|
+
"link": {
|
|
47
|
+
"description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.",
|
|
48
|
+
"oneOf": [
|
|
49
|
+
{
|
|
50
|
+
"description": "A string containing the link's URL.",
|
|
51
|
+
"type": "string",
|
|
52
|
+
"format": "uri"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"type": "object",
|
|
56
|
+
"required": [
|
|
57
|
+
"href"
|
|
58
|
+
],
|
|
59
|
+
"properties": {
|
|
60
|
+
"href": {
|
|
61
|
+
"description": "A string containing the link's URL.",
|
|
62
|
+
"type": "string",
|
|
63
|
+
"format": "uri"
|
|
64
|
+
},
|
|
65
|
+
"meta": {
|
|
66
|
+
"$ref": "#/definitions/meta"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"error": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"id": {
|
|
76
|
+
"description": "A unique identifier for this particular occurrence of the problem.",
|
|
77
|
+
"type": "string"
|
|
78
|
+
},
|
|
79
|
+
"links": {
|
|
80
|
+
"$ref": "#/definitions/links"
|
|
81
|
+
},
|
|
82
|
+
"status": {
|
|
83
|
+
"description": "The HTTP status code applicable to this problem, expressed as a string value.",
|
|
84
|
+
"type": "string"
|
|
85
|
+
},
|
|
86
|
+
"code": {
|
|
87
|
+
"description": "An application-specific error code, expressed as a string value.",
|
|
88
|
+
"type": "string"
|
|
89
|
+
},
|
|
90
|
+
"title": {
|
|
91
|
+
"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.",
|
|
92
|
+
"type": "string"
|
|
93
|
+
},
|
|
94
|
+
"detail": {
|
|
95
|
+
"description": "A human-readable explanation specific to this occurrence of the problem.",
|
|
96
|
+
"type": "string"
|
|
97
|
+
},
|
|
98
|
+
"source": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"properties": {
|
|
101
|
+
"pointer": {
|
|
102
|
+
"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].",
|
|
103
|
+
"type": "string"
|
|
104
|
+
},
|
|
105
|
+
"parameter": {
|
|
106
|
+
"description": "A string indicating which query parameter caused the error.",
|
|
107
|
+
"type": "string"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"meta": {
|
|
112
|
+
"$ref": "#/definitions/meta"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"additionalProperties": false
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|