apib2swagger 1.9.2 → 1.13.0
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/codeql-analysis.yml +71 -0
- package/.github/workflows/docker-publish.yml +70 -0
- package/.github/workflows/nodejs.yml +26 -0
- package/.travis.yml +3 -5
- package/Dockerfile +5 -0
- package/LICENSE +0 -0
- package/README.md +31 -5
- package/bin/apib2swagger.js +43 -17
- package/index.js +92 -374
- package/package.json +6 -4
- package/src/escape_json_pointer.js +0 -0
- package/src/mson_to_json_schema.js +13 -8
- package/src/parameters.js +111 -0
- package/src/requests.js +344 -0
- package/src/responses.js +181 -0
- package/src/util.js +127 -0
- package/test/input/Attributes.md +0 -0
- package/test/input/Issue-#15.md +0 -0
- package/test/input/Issue-#22.md +0 -0
- package/test/input/Issue-#23.md +0 -0
- package/test/input/Issue-#26.md +0 -0
- package/test/input/Issue-#29.md +0 -0
- package/test/input/Issue-#33.md +0 -0
- package/test/input/Issue-#35.md +0 -0
- package/test/input/Issue-#36.md +0 -0
- package/test/input/Issue-#38.md +0 -0
- package/test/input/Issue-#49.md +24 -0
- package/test/input/OpenAPI3_attributes.md +36 -0
- package/test/input/OpenAPI3_headers.md +74 -0
- package/test/input/OpenAPI3_includes.md +22 -0
- package/test/input/OpenAPI3_requests.md +162 -0
- package/test/input/OpenAPI3_responses.md +177 -0
- package/test/input/Schema.md +0 -0
- package/test/input/apiblueprint_uber.md +0 -0
- package/test/input/apiblueprint_valid_simple.md +0 -0
- package/test/input/includes/resource-request-body-2.json +3 -0
- package/test/input/includes/resource-request-body.json +3 -0
- package/test/input/includes/resource-request-schema.json +9 -0
- package/test/input/includes/resource-response-body-2.json +3 -0
- package/test/input/includes/resource-response-body.json +3 -0
- package/test/input/includes/resource-response-schema.json +9 -0
- package/test/input/schema_without_body.md +17 -0
- package/test/meta_schema.json +0 -0
- package/test/openAPI_30_schema.json +1662 -0
- package/test/output/01. Simplest API.json +1 -0
- package/test/output/01. Simplest API.ref.json +1 -0
- package/test/output/02. Resource and Actions.json +2 -0
- package/test/output/02. Resource and Actions.ref.json +2 -0
- package/test/output/03. Named Resource and Actions.json +2 -0
- package/test/output/03. Named Resource and Actions.ref.json +2 -0
- package/test/output/04. Grouping Resources.json +2 -0
- package/test/output/04. Grouping Resources.ref.json +2 -0
- package/test/output/05. Responses.json +7 -1
- package/test/output/05. Responses.ref.json +7 -1
- package/test/output/06. Requests.json +9 -3
- package/test/output/06. Requests.ref.json +9 -3
- package/test/output/07. Parameters.json +12 -5
- package/test/output/07. Parameters.ref.json +12 -5
- package/test/output/08. Attributes.json +1 -0
- package/test/output/08. Attributes.ref.json +1 -0
- package/test/output/09. Advanced Attributes.json +3 -0
- package/test/output/09. Advanced Attributes.ref.json +3 -0
- package/test/output/10. Data Structures.json +3 -0
- package/test/output/10. Data Structures.ref.json +3 -0
- package/test/output/11. Resource Model.json +7 -1
- package/test/output/11. Resource Model.ref.json +7 -1
- package/test/output/12. Advanced Action.json +3 -0
- package/test/output/12. Advanced Action.ref.json +3 -0
- package/test/output/13. Named Endpoints.json +12 -2
- package/test/output/13. Named Endpoints.ref.json +12 -2
- package/test/output/Attributes.json +1 -0
- package/test/output/Attributes.ref.json +1 -0
- package/test/output/Gist Fox API + Auth.json +47 -7
- package/test/output/Gist Fox API + Auth.ref.json +47 -7
- package/test/output/Issue-#15.json +3 -0
- package/test/output/Issue-#15.ref.json +3 -0
- package/test/output/Issue-#22.json +0 -0
- package/test/output/Issue-#22.ref.json +0 -0
- package/test/output/Issue-#23.json +1 -0
- package/test/output/Issue-#23.ref.json +1 -0
- package/test/output/Issue-#26.json +2 -0
- package/test/output/Issue-#26.ref.json +2 -0
- package/test/output/Issue-#29.json +2 -0
- package/test/output/Issue-#29.ref.json +2 -0
- package/test/output/Issue-#33.json +9 -8
- package/test/output/Issue-#33.ref.json +9 -8
- package/test/output/Issue-#35.json +0 -0
- package/test/output/Issue-#35.ref.json +0 -0
- package/test/output/Issue-#36.json +1 -0
- package/test/output/Issue-#36.ref.json +1 -0
- package/test/output/Issue-#38.bearer.json +53 -52
- package/test/output/Issue-#38.json +1 -0
- package/test/output/Issue-#38.ref.json +1 -0
- package/test/output/Issue-#49.json +141 -0
- package/test/output/Issue-#49.ref.json +126 -0
- package/test/output/OpenAPI3_attributes.json +199 -0
- package/test/output/OpenAPI3_attributes.ref.json +173 -0
- package/test/output/OpenAPI3_headers.json +169 -0
- package/test/output/OpenAPI3_headers.ref.json +169 -0
- package/test/output/OpenAPI3_includes.json +66 -0
- package/test/output/OpenAPI3_includes.ref.json +66 -0
- package/test/output/OpenAPI3_requests.json +326 -0
- package/test/output/OpenAPI3_requests.ref.json +321 -0
- package/test/output/OpenAPI3_responses.json +255 -0
- package/test/output/OpenAPI3_responses.ref.json +245 -0
- package/test/output/Schema.json +3 -0
- package/test/output/Schema.ref.json +3 -0
- package/test/output/apiblueprint_uber.json +5 -0
- package/test/output/apiblueprint_uber.ref.json +5 -0
- package/test/output/apiblueprint_valid_simple.json +47 -7
- package/test/output/apiblueprint_valid_simple.ref.json +47 -7
- package/test/output/schema_without_body.json +52 -0
- package/test/output/schema_without_body.ref.json +52 -0
- package/test/swagger_20_schema.json +0 -0
- package/test/test.js +31 -15
- package/test/util.test.js +151 -0
- package/.npmignore +0 -35
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
|
|
2
|
+
const getParamType = (name, uriTemplate, options) => {
|
|
3
|
+
const defaultType = options.openApi3 ? 'query' : 'body'
|
|
4
|
+
if (!uriTemplate) return defaultType;
|
|
5
|
+
for (var i = 0; i < uriTemplate.expressions.length; i++) {
|
|
6
|
+
var exp = uriTemplate.expressions[i];
|
|
7
|
+
if (!exp.varspecs) continue;
|
|
8
|
+
for (var j = 0; j < exp.varspecs.length; j++) {
|
|
9
|
+
var spec = exp.varspecs[j];
|
|
10
|
+
if (spec.varname === name) {
|
|
11
|
+
return exp.operator.symbol === '?' ? 'query' : 'path';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return defaultType;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports.processParameters = (parameters, uriTemplate, options) => {
|
|
19
|
+
const { openApi3 } = options
|
|
20
|
+
var PARAM_TYPES = {
|
|
21
|
+
'string': 'string',
|
|
22
|
+
'number': 'number',
|
|
23
|
+
'integer': 'integer',
|
|
24
|
+
'boolean': 'boolean', 'bool': 'boolean',
|
|
25
|
+
'array': 'array',
|
|
26
|
+
'file': 'file'
|
|
27
|
+
}
|
|
28
|
+
var params = [];
|
|
29
|
+
//console.log(parameters);
|
|
30
|
+
for (var l = 0; l < parameters.length; l++) {
|
|
31
|
+
var parameter = parameters[l];
|
|
32
|
+
//console.log(parameter);
|
|
33
|
+
// in = ["query", "header", "path", "formData", "body"]
|
|
34
|
+
var param = {
|
|
35
|
+
'name': parameter.name,
|
|
36
|
+
'in': getParamType(parameter.name, uriTemplate, options),
|
|
37
|
+
'description': parameter.description
|
|
38
|
+
};
|
|
39
|
+
if (openApi3) {
|
|
40
|
+
if (parameter.required) {
|
|
41
|
+
// Only add required if the value is true. False is not an accepted value.
|
|
42
|
+
param.required = true
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
param.required = parameter.required
|
|
47
|
+
}
|
|
48
|
+
if (parameter.example) {
|
|
49
|
+
if (openApi3) {
|
|
50
|
+
param.example = parameter.example
|
|
51
|
+
} else {
|
|
52
|
+
param['x-example'] = parameter.example
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var paramType = undefined;
|
|
57
|
+
if (PARAM_TYPES.hasOwnProperty(parameter.type)) {
|
|
58
|
+
paramType = PARAM_TYPES[parameter.type];
|
|
59
|
+
} else {
|
|
60
|
+
paramType = 'string';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var allowedValues = []
|
|
64
|
+
for (var j = 0; j < parameter.values.length; j++) {
|
|
65
|
+
allowedValues.push(parameter.values[j].value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
var parameterDefault = undefined;
|
|
69
|
+
if (parameter.default) {
|
|
70
|
+
if (paramType === 'number' || paramType === 'integer') {
|
|
71
|
+
parameterDefault = Number(parameter.default);
|
|
72
|
+
} else {
|
|
73
|
+
parameterDefault = parameter.default;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Body parameters has all info in schema
|
|
78
|
+
if (param.in === 'body') {
|
|
79
|
+
param.schema = {
|
|
80
|
+
'type': paramType
|
|
81
|
+
}
|
|
82
|
+
if (allowedValues.length > 0) {
|
|
83
|
+
param.schema.enum = allowedValues;
|
|
84
|
+
}
|
|
85
|
+
if (parameterDefault) {
|
|
86
|
+
param.schema.default = parameterDefault;
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
if (openApi3) {
|
|
90
|
+
param.schema = { type: paramType }
|
|
91
|
+
if (parameterDefault) {
|
|
92
|
+
param.schema.default = parameterDefault;
|
|
93
|
+
}
|
|
94
|
+
if (allowedValues.length > 0) {
|
|
95
|
+
param.schema.enum = allowedValues;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
param.type = paramType;
|
|
99
|
+
if (parameterDefault) {
|
|
100
|
+
param.default = parameterDefault;
|
|
101
|
+
}
|
|
102
|
+
if (allowedValues.length > 0) {
|
|
103
|
+
param.enum = allowedValues;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
params.push(param);
|
|
108
|
+
}
|
|
109
|
+
return params;
|
|
110
|
+
}
|
|
111
|
+
|
package/src/requests.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
const { hasFileRef, getRefFromInclude, searchDataStructure, generateSchemaFromExample } = require('./util')
|
|
2
|
+
const isEqual = require('lodash.isequal')
|
|
3
|
+
const escapeJSONPointer = require('./escape_json_pointer')
|
|
4
|
+
|
|
5
|
+
const swaggerHeaders = function (options, headers) {
|
|
6
|
+
var params = [];
|
|
7
|
+
const skipParams = options.openApi3 ? ['content-type'] : ['content-type', 'authorization']; // handled in another way
|
|
8
|
+
for (let i = 0; i < headers.length; i++) {
|
|
9
|
+
const element = headers[i];
|
|
10
|
+
if (skipParams.includes(element.name.toLowerCase())) continue;
|
|
11
|
+
var param = {
|
|
12
|
+
'name': element.name,
|
|
13
|
+
'in': 'header',
|
|
14
|
+
'description': `e.g. ${element.value}`,
|
|
15
|
+
'required': false
|
|
16
|
+
};
|
|
17
|
+
if (options.openApi3) {
|
|
18
|
+
param.schema = { type: 'string' }
|
|
19
|
+
param.example = element.value
|
|
20
|
+
} else {
|
|
21
|
+
param['x-example'] = element.value
|
|
22
|
+
param.type = 'string' // TODO: string, number, boolean, integer, array
|
|
23
|
+
}
|
|
24
|
+
if (!params.find(p => isEqual(p, param)))
|
|
25
|
+
params.push(param);
|
|
26
|
+
}
|
|
27
|
+
return params;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// generate security and securityDefinitions from authorization headers
|
|
31
|
+
function swaggerSecurity(context, headers) {
|
|
32
|
+
var security = null;
|
|
33
|
+
headers.filter(function (header) {
|
|
34
|
+
return header.name.toLowerCase() === 'authorization';
|
|
35
|
+
}).forEach(function (header) {
|
|
36
|
+
if (header.value.match(/^Basic /)) {
|
|
37
|
+
if (!security) security = {};
|
|
38
|
+
security['basic'] = [];
|
|
39
|
+
context.swagger.securityDefinitions['basic'] = { type: 'basic' };
|
|
40
|
+
} else if (header.value.match(/^Bearer /)) {
|
|
41
|
+
if (!security) security = {};
|
|
42
|
+
if (context.options.bearerAsApikey) {
|
|
43
|
+
security['bearer'] = [];
|
|
44
|
+
context.swagger.securityDefinitions['bearer'] = {
|
|
45
|
+
type: 'apiKey', in: 'header', name: 'Authorization'
|
|
46
|
+
};
|
|
47
|
+
} else {
|
|
48
|
+
security['oauth2'] = [];
|
|
49
|
+
context.swagger.securityDefinitions['oauth2'] = {
|
|
50
|
+
type: 'oauth2', flow: 'accessCode',
|
|
51
|
+
authorizationUrl: '', tokenUrl: '', scopes: {}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return security;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const setSecurity = (context, request, operation) => {
|
|
60
|
+
if (context.options.openApi3){
|
|
61
|
+
return operation
|
|
62
|
+
}
|
|
63
|
+
var security = swaggerSecurity(context, request.headers);
|
|
64
|
+
if (security) {
|
|
65
|
+
if (!operation.security) {
|
|
66
|
+
operation.security = [security];
|
|
67
|
+
} else {
|
|
68
|
+
if (!operation.security.find(s => isEqual(s, security))){
|
|
69
|
+
operation.security.push(security);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return operation
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const setSwaggerRequestSchema = (operation, schema) => {
|
|
77
|
+
if (!schema.length) {
|
|
78
|
+
return operation
|
|
79
|
+
}
|
|
80
|
+
if (schema.length === 1) {
|
|
81
|
+
operation.parameters.push({ name: 'body', in: 'body', schema: schema[0].scheme })
|
|
82
|
+
return operation
|
|
83
|
+
}
|
|
84
|
+
operation.parameters.push({
|
|
85
|
+
name: 'body',
|
|
86
|
+
in: 'body',
|
|
87
|
+
schema: {
|
|
88
|
+
anyOf: schema.map((s) => s.scheme)
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return operation
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const mergeHeaders = (headers, parameters, options) => {
|
|
95
|
+
const existingHeaders = parameters
|
|
96
|
+
.filter(param => param.in === 'header')
|
|
97
|
+
.map(param => param.name.toLowerCase());
|
|
98
|
+
|
|
99
|
+
let nonDuplicateHeaders
|
|
100
|
+
if (options.openApi3) {
|
|
101
|
+
// We are comparing the whole object instead of just the name to allow duplicate names.
|
|
102
|
+
// In doing so, we allow multiple types of Authorization headers to be specified.
|
|
103
|
+
nonDuplicateHeaders = headers.filter(header =>
|
|
104
|
+
!parameters.find(p => isEqual(p, header))
|
|
105
|
+
)
|
|
106
|
+
} else {
|
|
107
|
+
nonDuplicateHeaders = headers.filter(header =>
|
|
108
|
+
!existingHeaders.includes(header.name.toLowerCase())
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return parameters.concat(nonDuplicateHeaders);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const processRequestSchema = (request, options) => {
|
|
115
|
+
let schema
|
|
116
|
+
const { openApi3 } = options
|
|
117
|
+
if (openApi3 && hasFileRef(request.schema)) {
|
|
118
|
+
return getRefFromInclude(request.schema)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// referencing Model's Schema is also here (no need to reference definitions)
|
|
122
|
+
try {
|
|
123
|
+
schema = JSON.parse(request.schema);
|
|
124
|
+
delete schema['$schema'];
|
|
125
|
+
} catch (e){
|
|
126
|
+
// If we can't parse the request schema we have nothing left to do here.
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
// if we have example values in the body then insert them into the json schema
|
|
131
|
+
if (!openApi3) {
|
|
132
|
+
var body = JSON.parse(request.body);
|
|
133
|
+
|
|
134
|
+
if (schema['type'] === 'object') {
|
|
135
|
+
schema.example = body;
|
|
136
|
+
} else if (schema['type'] === 'array') {
|
|
137
|
+
schema.items.example = body;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Catch any error from parsing the request body. However, if there is an error
|
|
141
|
+
// (ex. no request body given), we still want to keep the schema.
|
|
142
|
+
} catch (e) {}
|
|
143
|
+
return schema
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const processRequestAttributes = (request, options, contentType) => {
|
|
147
|
+
const schema = []
|
|
148
|
+
const scheme = searchDataStructure(request.content, options); // Attributes 4
|
|
149
|
+
if (scheme) schema.push({ scheme, contentType });
|
|
150
|
+
if (request.reference) {
|
|
151
|
+
const componentsPath = options.openApi3 ? '#/components/schemas' : '#/definitions/'
|
|
152
|
+
schema.push({
|
|
153
|
+
scheme: {
|
|
154
|
+
'$ref': componentsPath + escapeJSONPointer(request.reference.id + 'Model')
|
|
155
|
+
},
|
|
156
|
+
contentType
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return schema
|
|
160
|
+
}
|
|
161
|
+
const buildBodyExamples = (requestBody, bodyExamples, contentType) => {
|
|
162
|
+
let body
|
|
163
|
+
if (typeof requestBody === 'string') {
|
|
164
|
+
if (hasFileRef(requestBody)){
|
|
165
|
+
body = getRefFromInclude(requestBody)
|
|
166
|
+
} else {
|
|
167
|
+
try {
|
|
168
|
+
body = JSON.parse(requestBody);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
if (contentType === 'text/plain') {
|
|
171
|
+
body = requestBody
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (typeof body !== 'object' && typeof body !== 'array' && typeof body !== 'string') {
|
|
178
|
+
return bodyExamples
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!bodyExamples[contentType]) {
|
|
182
|
+
bodyExamples[contentType] = { example: body }
|
|
183
|
+
} else if (bodyExamples[contentType].examples) {
|
|
184
|
+
const count = Object.keys(bodyExamples[contentType].examples).length + 1
|
|
185
|
+
const name = 'example' + count
|
|
186
|
+
bodyExamples[contentType].examples[name] = body.$ref ? body : { value: body }
|
|
187
|
+
} else {
|
|
188
|
+
const existingExample = bodyExamples[contentType].example
|
|
189
|
+
bodyExamples[contentType].examples = {
|
|
190
|
+
example1: existingExample.$ref ? existingExample : { value: existingExample },
|
|
191
|
+
example2: body.$ref ? body : { value: body }
|
|
192
|
+
}
|
|
193
|
+
delete bodyExamples[contentType].example
|
|
194
|
+
}
|
|
195
|
+
return bodyExamples
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const getOpenApiRequestSchema = (request, schema, contentType, options) => {
|
|
199
|
+
/*
|
|
200
|
+
With OpenAPI3 we can make full use of Attributes because examples no longer live
|
|
201
|
+
on the schema object and therefore, we don't lose our examples when we use
|
|
202
|
+
Attributes as the schema definition.
|
|
203
|
+
*/
|
|
204
|
+
const contentTypeScheme = {}
|
|
205
|
+
if (!options.preferReference) {
|
|
206
|
+
if (request.schema) { // Schema section in Request section
|
|
207
|
+
contentTypeScheme[contentType] = processRequestSchema(request, options)
|
|
208
|
+
}
|
|
209
|
+
if (contentTypeScheme[contentType]) {
|
|
210
|
+
schema.push({ scheme: contentTypeScheme[contentType], contentType });
|
|
211
|
+
} else {
|
|
212
|
+
const attributes = processRequestAttributes(request, options, contentType)
|
|
213
|
+
schema.push(...attributes)
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
const attributes = processRequestAttributes(request, options, contentType)
|
|
217
|
+
if (attributes.length) {
|
|
218
|
+
schema.push(...attributes)
|
|
219
|
+
} else {
|
|
220
|
+
contentTypeScheme[contentType] = processRequestSchema(request, options)
|
|
221
|
+
if (contentTypeScheme[contentType]) {
|
|
222
|
+
schema.push({ scheme: contentTypeScheme[contentType], contentType });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// If there are no schema but we have a body example, try to generate a schema from it.
|
|
227
|
+
// We stop at 1 auto-generated schema.
|
|
228
|
+
if (request.body && schema.length === 0) {
|
|
229
|
+
contentTypeScheme[contentType] = generateSchemaFromExample(request.headers, request.body, options);
|
|
230
|
+
if (contentTypeScheme[contentType]){
|
|
231
|
+
schema.push({ scheme: contentTypeScheme[contentType], contentType });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return schema
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const setOpenApiRequestSchema = (operation, schema) => {
|
|
238
|
+
if (!schema.length){
|
|
239
|
+
return operation
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Make sure all content types exist to make setting the schemes easier.
|
|
243
|
+
schema.forEach(s => {
|
|
244
|
+
if (!operation.requestBody.content[s.contentType]) {
|
|
245
|
+
operation.requestBody.content[s.contentType] = {}
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
if (schema.length === 1) {
|
|
250
|
+
if (!operation.requestBody.content[schema[0].contentType]){
|
|
251
|
+
operation.requestBody.content[schema[0].contentType] = schema[0].scheme
|
|
252
|
+
} else {
|
|
253
|
+
operation.requestBody.content[schema[0].contentType].schema = schema[0].scheme
|
|
254
|
+
}
|
|
255
|
+
return operation
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
schema.forEach(s => {
|
|
259
|
+
if (!operation.requestBody.content[s.contentType].schema){
|
|
260
|
+
operation.requestBody.content[s.contentType].schema = s.scheme
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
// If a schema exist for this content type,
|
|
264
|
+
// we need to use oneOf because we have an additional schema to add.
|
|
265
|
+
const { oneOf } = operation.requestBody.content[s.contentType].schema
|
|
266
|
+
if (oneOf){
|
|
267
|
+
// If oneOf already exists we can just add the new schema if its not a duplicate.
|
|
268
|
+
if (!oneOf.find(sc => isEqual(sc, s.scheme))){
|
|
269
|
+
operation.requestBody.content[s.contentType].schema.oneOf.push(s.scheme)
|
|
270
|
+
}
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
const existing = operation.requestBody.content[s.contentType].schema
|
|
274
|
+
if (isEqual(existing, s.scheme)){
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
// If oneOf does not exist and its not a duplicate, we need to create it,
|
|
278
|
+
// making sure to include the existing schema.
|
|
279
|
+
operation.requestBody.content[s.contentType].schema = {
|
|
280
|
+
oneOf: [
|
|
281
|
+
operation.requestBody.content[s.contentType].schema, // existing
|
|
282
|
+
s.scheme // additional
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
return operation
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports.processRequests = (operation, action, context) => {
|
|
290
|
+
const { options } = context
|
|
291
|
+
const { openApi3 } = options
|
|
292
|
+
var schema = [],
|
|
293
|
+
scheme = searchDataStructure(action.content, options); // Attributes 3
|
|
294
|
+
if (scheme) schema.push({ contentType: 'application/json', scheme });
|
|
295
|
+
|
|
296
|
+
let bodyExamples = {}
|
|
297
|
+
|
|
298
|
+
for (var j = 0; j < action.examples.length; j++) {
|
|
299
|
+
var example = action.examples[j];
|
|
300
|
+
for (var l = 0; l < example.requests.length; l++) {
|
|
301
|
+
var request = example.requests[l];
|
|
302
|
+
|
|
303
|
+
operation = setSecurity(context, request, operation)
|
|
304
|
+
|
|
305
|
+
var headers = swaggerHeaders(options, request.headers);
|
|
306
|
+
if (headers) {
|
|
307
|
+
operation.parameters = mergeHeaders(headers, operation.parameters, options)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const contentTypeHeader = request.headers.find((h) => h.name === 'Content-Type')
|
|
311
|
+
const contentType = contentTypeHeader ? contentTypeHeader.value : 'application/json'
|
|
312
|
+
|
|
313
|
+
if (request.body && openApi3) {
|
|
314
|
+
bodyExamples = buildBodyExamples(request.body, bodyExamples, contentType)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!openApi3) {
|
|
318
|
+
// Build schemas and examples for swagger 2.0
|
|
319
|
+
if (request.schema) {
|
|
320
|
+
scheme = processRequestSchema(request, options)
|
|
321
|
+
if (scheme) schema.push({ scheme, contentType });
|
|
322
|
+
} else {
|
|
323
|
+
const attributes = processRequestAttributes(request, options, contentType)
|
|
324
|
+
schema.push(...attributes)
|
|
325
|
+
// fall back to body
|
|
326
|
+
if (request.body && schema.length === 0) {
|
|
327
|
+
scheme = generateSchemaFromExample(request.headers, request.body, options);
|
|
328
|
+
if (scheme) schema.push({ scheme, contentType });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
schema = getOpenApiRequestSchema(request, schema, contentType, options)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (openApi3 && (schema.length || Object.keys(bodyExamples).length)){
|
|
337
|
+
operation.requestBody = { content: {} }
|
|
338
|
+
if (Object.keys(bodyExamples).length) {
|
|
339
|
+
operation.requestBody.content = bodyExamples
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return openApi3 ? setOpenApiRequestSchema(operation, schema) : setSwaggerRequestSchema(operation, schema)
|
|
344
|
+
}
|
package/src/responses.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const { fixArraySchema, hasFileRef, getRefFromInclude, searchDataStructure } = require('./util')
|
|
2
|
+
const escapeJSONPointer = require('./escape_json_pointer')
|
|
3
|
+
const isEqual = require('lodash.isequal')
|
|
4
|
+
const http = require('http')
|
|
5
|
+
|
|
6
|
+
const parseResponseSchema = (schema, options) => {
|
|
7
|
+
if (!schema) return
|
|
8
|
+
if (options.openApi3 && hasFileRef(schema)){
|
|
9
|
+
return getRefFromInclude(schema)
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const result = JSON.parse(schema);
|
|
13
|
+
delete result['$schema'];
|
|
14
|
+
fixArraySchema(result); // work around for Swagger UI / Editor
|
|
15
|
+
return result
|
|
16
|
+
} catch (e) { }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const parseResponseBody = (body, header, options) => {
|
|
20
|
+
if (options.openApi3 && hasFileRef(body)){
|
|
21
|
+
return getRefFromInclude(body)
|
|
22
|
+
}
|
|
23
|
+
if (!header.value.match(/application\/.*json/)) {
|
|
24
|
+
return body
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(body);
|
|
28
|
+
} catch (e) { }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const getResponseSchema = (response, options) => {
|
|
32
|
+
const componentsPath = options.openApi3 ? '#/components/schemas' : '#/definitions/'
|
|
33
|
+
if (options.preferReference) { // MSON then schema
|
|
34
|
+
const inputSchema = searchDataStructure(response.content, options); // Attributes in response
|
|
35
|
+
if (inputSchema) {
|
|
36
|
+
return inputSchema
|
|
37
|
+
} else if (response.reference) {
|
|
38
|
+
return {
|
|
39
|
+
'$ref': componentsPath + escapeJSONPointer(response.reference.id + 'Model')
|
|
40
|
+
};
|
|
41
|
+
} else if (response.schema) {
|
|
42
|
+
return parseResponseSchema(response.schema, options)
|
|
43
|
+
}
|
|
44
|
+
} else { // schema then MSON
|
|
45
|
+
if (response.schema) {
|
|
46
|
+
const schema = parseResponseSchema(response.schema, options)
|
|
47
|
+
if (schema) {
|
|
48
|
+
return schema
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const inputSchema = searchDataStructure(response.content, options); // Attributes in response
|
|
53
|
+
if (inputSchema) return inputSchema;
|
|
54
|
+
if (response.reference) {
|
|
55
|
+
return {
|
|
56
|
+
'$ref': componentsPath + escapeJSONPointer(response.reference.id + 'Model')
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const setResponseSchema = (responses, response, schema, options) => {
|
|
63
|
+
if (!options.openApi3){
|
|
64
|
+
responses[response.name].schema = schema
|
|
65
|
+
return responses
|
|
66
|
+
}
|
|
67
|
+
// In openAPI 3 the schema lives under the content type
|
|
68
|
+
const contentTypeHeader = response.headers.find((h) => h.name.toLowerCase() === 'content-type')
|
|
69
|
+
if (!contentTypeHeader.value) {
|
|
70
|
+
return responses
|
|
71
|
+
}
|
|
72
|
+
if (!responses[response.name].content[contentTypeHeader.value]){
|
|
73
|
+
responses[response.name].content[contentTypeHeader.value] = {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!responses[response.name].content[contentTypeHeader.value].schema){
|
|
77
|
+
responses[response.name].content[contentTypeHeader.value].schema = schema
|
|
78
|
+
return responses
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// If a schema already exists, we need to use oneOf for additional unique schemas.
|
|
82
|
+
let existingSchema = { ...responses[response.name].content[contentTypeHeader.value].schema }
|
|
83
|
+
// It is possible that the given schema is a duplicate. If that's the case, we don't add it.
|
|
84
|
+
if (isEqual(existingSchema, schema)){
|
|
85
|
+
return responses
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (existingSchema.oneOf){
|
|
89
|
+
if (!existingSchema.oneOf.find((s) => isEqual(s, schema))){
|
|
90
|
+
responses[response.name].content[contentTypeHeader.value].schema.oneOf.push(schema)
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
responses[response.name].content[contentTypeHeader.value].schema = { oneOf: [existingSchema, schema] }
|
|
94
|
+
}
|
|
95
|
+
return responses
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const setResponseExample = (responses, response, header, body, options) => {
|
|
99
|
+
if (!options.openApi3) {
|
|
100
|
+
// Sample path to Swagger 2.0 example:
|
|
101
|
+
// responses -> 200 -> examples -> application/json -> { }
|
|
102
|
+
responses[response.name].examples[header.value] = body;
|
|
103
|
+
return responses
|
|
104
|
+
}
|
|
105
|
+
if (!body) {
|
|
106
|
+
return responses
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Sample path to OpenAPI 3.0 examples:
|
|
110
|
+
// responses -> 200 -> content -> application/json -> example -> { }
|
|
111
|
+
// responses -> 200 -> content -> application/json -> examples -> example1 -> { }
|
|
112
|
+
const existingContainer = responses[response.name].content[header.value]
|
|
113
|
+
if (!existingContainer){
|
|
114
|
+
responses[response.name].content[header.value] = { example: body };
|
|
115
|
+
} else if (existingContainer.examples) {
|
|
116
|
+
const count = Object.keys(existingContainer.examples).length + 1
|
|
117
|
+
const exampleName = 'example' + count
|
|
118
|
+
responses[response.name].content[header.value].examples[exampleName] = body.$ref ? body : { value: body };
|
|
119
|
+
} else if (existingContainer.example) {
|
|
120
|
+
const existingExample = existingContainer.example
|
|
121
|
+
responses[response.name].content[header.value].examples = {
|
|
122
|
+
example1: existingExample.$ref ? existingExample : { value: existingExample },
|
|
123
|
+
example2: body.$ref ? body : { value: body }
|
|
124
|
+
}
|
|
125
|
+
delete responses[response.name].content[header.value].example
|
|
126
|
+
} else if (!existingContainer.example) {
|
|
127
|
+
responses[response.name].content[header.value].example = body
|
|
128
|
+
}
|
|
129
|
+
return responses
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports.processResponses = (examples, options) => {
|
|
133
|
+
let responses = {};
|
|
134
|
+
const { openApi3 } = options;
|
|
135
|
+
for (var l = 0; l < examples.length; l++) {
|
|
136
|
+
var example = examples[l];
|
|
137
|
+
for (var m = 0; m < example.responses.length; m++) {
|
|
138
|
+
var response = example.responses[m];
|
|
139
|
+
|
|
140
|
+
if (!responses[response.name]) {
|
|
141
|
+
responses[response.name] = { description: {}, headers: {} }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
responses[response.name].description = response.description || http.STATUS_CODES[response.name];
|
|
145
|
+
if (openApi3) {
|
|
146
|
+
// Does not overwrite and allows multiple examples.
|
|
147
|
+
if (!responses[response.name].content){
|
|
148
|
+
responses[response.name].content = {};
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// Overwrites every time
|
|
152
|
+
responses[response.name].examples = {}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const schema = getResponseSchema(response, options)
|
|
156
|
+
|
|
157
|
+
if (schema){
|
|
158
|
+
responses = setResponseSchema(responses, response, schema, options)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (var n = 0; n < response.headers.length; n++) {
|
|
162
|
+
var header = response.headers[n];
|
|
163
|
+
if (header.name.toLowerCase() === 'content-type') {
|
|
164
|
+
let body = parseResponseBody(response.body, header, options)
|
|
165
|
+
if (body || body === '') {
|
|
166
|
+
responses = setResponseExample(responses, response, header, body, options)
|
|
167
|
+
}
|
|
168
|
+
} else if (header.name.toLowerCase() !== 'authorization') {
|
|
169
|
+
if (openApi3) {
|
|
170
|
+
responses[response.name].headers[header.name] = {
|
|
171
|
+
schema: { 'type': 'string' }
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
responses[response.name].headers[header.name] = { 'type': 'string' }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return responses;
|
|
181
|
+
}
|