apib2swagger 1.10.0 → 1.14.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 +2 -3
- package/Dockerfile +5 -0
- package/LICENSE +0 -0
- package/README.md +31 -10
- package/bin/apib2swagger.js +44 -18
- package/index.js +82 -366
- 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 +2 -0
- package/test/output/05. Responses.ref.json +2 -0
- package/test/output/06. Requests.json +2 -0
- package/test/output/06. Requests.ref.json +2 -0
- package/test/output/07. Parameters.json +3 -0
- package/test/output/07. Parameters.ref.json +3 -0
- 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 +2 -0
- package/test/output/11. Resource Model.ref.json +2 -0
- 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 +2 -0
- package/test/output/13. Named Endpoints.ref.json +2 -0
- package/test/output/Attributes.json +1 -0
- package/test/output/Attributes.ref.json +1 -0
- package/test/output/Gist Fox API + Auth.json +12 -0
- package/test/output/Gist Fox API + Auth.ref.json +12 -0
- 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 +12 -0
- package/test/output/apiblueprint_valid_simple.ref.json +12 -0
- 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
- package/package-lock.json +0 -2320
package/index.js
CHANGED
|
@@ -1,36 +1,55 @@
|
|
|
1
1
|
var url = require('url'),
|
|
2
|
-
http = require('http'),
|
|
3
2
|
drafter = require('drafter.js'),
|
|
4
3
|
UriTemplate = require('uritemplate'),
|
|
5
|
-
|
|
4
|
+
jsonSchemaFromMSON = require('./src/mson_to_json_schema');
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const { searchDataStructure, generateSchemaFromExample } = require('./src/util')
|
|
7
|
+
const { processRequests } = require('./src/requests')
|
|
8
|
+
const { processResponses } = require('./src/responses')
|
|
9
|
+
const { processParameters } = require('./src/parameters')
|
|
9
10
|
|
|
10
11
|
var apib2swagger = module.exports.convertParsed = function (apib, options) {
|
|
11
|
-
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
const { openApi3, infoTitle } = options
|
|
13
|
+
var output = {};
|
|
14
|
+
if (openApi3) {
|
|
15
|
+
output.openapi = '3.0.3';
|
|
16
|
+
} else {
|
|
17
|
+
output.swagger = '2.0';
|
|
18
|
+
}
|
|
19
|
+
output.info = {
|
|
20
|
+
'title': infoTitle || apib.name,
|
|
21
|
+
'version': openApi3 ? '1.0.0' : '',
|
|
17
22
|
'description': apib.description
|
|
18
23
|
}
|
|
19
24
|
apib.metadata.forEach(function (meta) {
|
|
20
|
-
//console.log(meta);
|
|
21
25
|
if (meta.name.toLowerCase() === 'host') {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if (openApi3) {
|
|
27
|
+
output.servers = [
|
|
28
|
+
{
|
|
29
|
+
url: meta.value
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
} else {
|
|
33
|
+
var urlParts = url.parse(meta.value);
|
|
34
|
+
output.host = urlParts.host;
|
|
35
|
+
output.basePath = urlParts.pathname;
|
|
36
|
+
output.schemes = [urlParts.protocol.replace(':', '')];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (meta.name.toLowerCase() === 'version' && meta.value) {
|
|
40
|
+
output.info.version = meta.value
|
|
28
41
|
}
|
|
29
42
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
output.paths = {};
|
|
44
|
+
|
|
45
|
+
if (openApi3) {
|
|
46
|
+
output.components = { schemas: {} }
|
|
47
|
+
} else {
|
|
48
|
+
output.definitions = {}
|
|
49
|
+
output.securityDefinitions = {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
var converterContext = { swagger: output, options: options };
|
|
34
53
|
var tags = {};
|
|
35
54
|
apib.content.filter(function (content) {
|
|
36
55
|
return content.element === 'category';
|
|
@@ -42,21 +61,32 @@ var apib2swagger = module.exports.convertParsed = function (apib, options) {
|
|
|
42
61
|
category.content.forEach(function (content) {
|
|
43
62
|
if (content.element === 'resource') {
|
|
44
63
|
// (name, description) in Resource section are discarded
|
|
45
|
-
swaggerDefinitions(
|
|
64
|
+
const definitions = swaggerDefinitions(content, options);
|
|
65
|
+
if (openApi3) {
|
|
66
|
+
output.components.schemas = { ...output.components.schemas, ...definitions }
|
|
67
|
+
} else {
|
|
68
|
+
output.definitions = { ...output.definitions, ...definitions }
|
|
69
|
+
}
|
|
46
70
|
swaggerPaths(converterContext, groupName, content);
|
|
47
71
|
} else if (content.element === 'copy') {
|
|
48
72
|
// group description here
|
|
49
73
|
tags[groupName].description = content.content;
|
|
50
74
|
} else if (content.element === 'dataStructure') {
|
|
51
|
-
|
|
75
|
+
const { id } = content.content[0].meta
|
|
76
|
+
const schema = jsonSchemaFromMSON(content, options);
|
|
77
|
+
if (openApi3) {
|
|
78
|
+
output.components.schemas[id] = schema
|
|
79
|
+
} else {
|
|
80
|
+
output.definitions[id] = schema
|
|
81
|
+
}
|
|
52
82
|
}
|
|
53
83
|
});
|
|
54
84
|
});
|
|
55
|
-
|
|
85
|
+
output.tags = [];
|
|
56
86
|
for (var key in tags) {
|
|
57
|
-
|
|
87
|
+
output.tags.push(tags[key]);
|
|
58
88
|
}
|
|
59
|
-
return
|
|
89
|
+
return output;
|
|
60
90
|
}
|
|
61
91
|
|
|
62
92
|
function swaggerPathName(uriTemplate) {
|
|
@@ -73,29 +103,43 @@ function swaggerPathName(uriTemplate) {
|
|
|
73
103
|
return decodeURIComponent(uriTemplate.expand(params));
|
|
74
104
|
}
|
|
75
105
|
|
|
76
|
-
var swaggerDefinitions = function (
|
|
106
|
+
var swaggerDefinitions = function (resource, options) {
|
|
77
107
|
var scheme;
|
|
108
|
+
const result = {}
|
|
109
|
+
const { openApi3 } = options
|
|
78
110
|
if (resource.name) {
|
|
79
|
-
scheme = searchDataStructure(resource.content); // Attributes 1
|
|
80
|
-
|
|
111
|
+
scheme = searchDataStructure(resource.content, options); // Attributes 1
|
|
112
|
+
if (openApi3) {
|
|
113
|
+
if (scheme) {
|
|
114
|
+
result[resource.name] = scheme
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
result[resource.name] = scheme ? scheme : {};
|
|
118
|
+
}
|
|
81
119
|
}
|
|
82
120
|
const model = resource.model;
|
|
83
121
|
if (model.content && model.name) {
|
|
84
|
-
scheme = searchDataStructure(model.content); // Attribute 2
|
|
122
|
+
scheme = searchDataStructure(model.content, options); // Attribute 2
|
|
85
123
|
// fall back to body
|
|
86
124
|
if (!scheme && model.content.length > 0) {
|
|
87
|
-
scheme = generateSchemaFromExample(model.headers, model.content[0].content);
|
|
125
|
+
scheme = generateSchemaFromExample(model.headers, model.content[0].content, options);
|
|
126
|
+
}
|
|
127
|
+
if (openApi3) {
|
|
128
|
+
if (scheme) {
|
|
129
|
+
result[model.name + 'Model'] = scheme
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
result[model.name + 'Model'] = scheme ? scheme : {};
|
|
88
133
|
}
|
|
89
|
-
definitions[model.name + 'Model'] = scheme ? scheme : {};
|
|
90
134
|
}
|
|
135
|
+
return result
|
|
91
136
|
};
|
|
92
137
|
|
|
93
138
|
var swaggerPaths = function (context, tag, resource) {
|
|
94
139
|
var paths = context.swagger.paths;
|
|
95
140
|
var uriTemplate = UriTemplate.parse(resource.uriTemplate),
|
|
96
141
|
pathName = swaggerPathName(uriTemplate);
|
|
97
|
-
|
|
98
|
-
var pathParams = swaggerParameters(resource.parameters, uriTemplate); // for swagger ui
|
|
142
|
+
var pathParams = processParameters(resource.parameters, uriTemplate, context.options); // for swagger ui
|
|
99
143
|
for (var k = 0; k < resource.actions.length; k++) {
|
|
100
144
|
var action = resource.actions[k];
|
|
101
145
|
if (!action.attributes.uriTemplate) {
|
|
@@ -112,11 +156,12 @@ var swaggerPaths = function (context, tag, resource) {
|
|
|
112
156
|
|
|
113
157
|
var swaggerOperation = function (context, pathParams, uriTemplate, action, tag) {
|
|
114
158
|
var operation = {
|
|
115
|
-
'responses':
|
|
159
|
+
'responses': processResponses(action.examples, context.options),
|
|
116
160
|
'summary': action.name,
|
|
161
|
+
'operationId': action.name,
|
|
117
162
|
'description': action.description,
|
|
118
163
|
'tags': tag ? [tag] : [],
|
|
119
|
-
'parameters': pathParams.concat(
|
|
164
|
+
'parameters': pathParams.concat(processParameters(action.parameters, uriTemplate, context.options))
|
|
120
165
|
};
|
|
121
166
|
var produces = {}, producesExist = false;
|
|
122
167
|
for (var key in operation.responses) {
|
|
@@ -132,332 +177,7 @@ var swaggerOperation = function (context, pathParams, uriTemplate, action, tag)
|
|
|
132
177
|
operation.produces.push(mime);
|
|
133
178
|
}
|
|
134
179
|
}
|
|
135
|
-
|
|
136
|
-
var schema = [],
|
|
137
|
-
scheme = searchDataStructure(action.content); // Attributes 3
|
|
138
|
-
if (scheme) schema.push(scheme);
|
|
139
|
-
for (var j = 0; j < action.examples.length; j++) {
|
|
140
|
-
var example = action.examples[j];
|
|
141
|
-
for (var l = 0; l < example.requests.length; l++) {
|
|
142
|
-
var request = example.requests[l];
|
|
143
|
-
|
|
144
|
-
// TODO should apply to Model Section?
|
|
145
|
-
var security = swaggerSecurity(context, request.headers);
|
|
146
|
-
if (security) {
|
|
147
|
-
if (!operation.security) {
|
|
148
|
-
operation.security = [security];
|
|
149
|
-
} else {
|
|
150
|
-
// TODO remove duplications
|
|
151
|
-
operation.security.push(security);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
var headers = swaggerHeaders(context, request.headers);
|
|
156
|
-
if (headers) {
|
|
157
|
-
const existingHeaders = operation.parameters
|
|
158
|
-
.filter(param => param.in === 'header')
|
|
159
|
-
.map(param => param.name.toLowerCase());
|
|
160
|
-
const nonDuplicateHeaders = headers.filter(header => !existingHeaders.includes(header.name.toLowerCase()));
|
|
161
|
-
operation.parameters = operation.parameters.concat(nonDuplicateHeaders);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (request.schema) { // Schema section in Request section
|
|
165
|
-
try {
|
|
166
|
-
// referencing Model's Schema is also here (no need to referencing defenitions)
|
|
167
|
-
scheme = JSON.parse(request.schema);
|
|
168
|
-
delete scheme['$schema'];
|
|
169
|
-
|
|
170
|
-
// if we have example values in the body then insert them into the json schema
|
|
171
|
-
var body = JSON.parse(request.body);
|
|
172
|
-
if (scheme['type'] === 'object') {
|
|
173
|
-
scheme.example = body;
|
|
174
|
-
} else if (scheme['type'] === 'array') {
|
|
175
|
-
scheme.items.example = body;
|
|
176
|
-
}
|
|
177
|
-
if (scheme) schema.push(scheme);
|
|
178
|
-
} catch (e) { }
|
|
179
|
-
} else {
|
|
180
|
-
scheme = searchDataStructure(request.content); // Attributes 4
|
|
181
|
-
if (scheme) schema.push(scheme);
|
|
182
|
-
if (request.reference) {
|
|
183
|
-
schema.push({ '$ref': '#/definitions/' + escapeJSONPointer(request.reference.id + 'Model') });
|
|
184
|
-
}
|
|
185
|
-
// fall back to body
|
|
186
|
-
if (request.body && (schema == null || schema.length == 0)) {
|
|
187
|
-
scheme = generateSchemaFromExample(request.headers, request.body);
|
|
188
|
-
if (scheme) schema.push(scheme);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (schema.length == 1) {
|
|
194
|
-
operation.parameters.push({ name: 'body', in: 'body', schema: schema[0] });
|
|
195
|
-
} else if (schema.length > 1) {
|
|
196
|
-
operation.parameters.push({ name: 'body', in: 'body', schema: { anyOf: schema } });
|
|
197
|
-
}
|
|
198
|
-
return operation;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
var swaggerHeaders = function (context, headers) {
|
|
202
|
-
var params = [];
|
|
203
|
-
const skipParams = ['content-type', 'authorization']; // handled in another way
|
|
204
|
-
for (let i = 0; i < headers.length; i++) {
|
|
205
|
-
const element = headers[i];
|
|
206
|
-
if (skipParams.includes(element.name.toLowerCase())) continue;
|
|
207
|
-
var param = {
|
|
208
|
-
'name': element.name,
|
|
209
|
-
'in': 'header',
|
|
210
|
-
'description': `e.g. ${element.value}`,
|
|
211
|
-
'required': false,
|
|
212
|
-
'x-example': element.value,
|
|
213
|
-
'type': 'string' // TODO: string, number, boolean, integer, array
|
|
214
|
-
};
|
|
215
|
-
params.push(param);
|
|
216
|
-
}
|
|
217
|
-
return params;
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
// generate security and securityDefinitions from authorization headers
|
|
221
|
-
function swaggerSecurity(context, headers) {
|
|
222
|
-
var security = null;
|
|
223
|
-
headers.filter(function (header) {
|
|
224
|
-
return header.name.toLowerCase() === 'authorization';
|
|
225
|
-
}).forEach(function (header) {
|
|
226
|
-
if (header.value.match(/^Basic /)) {
|
|
227
|
-
if (!security) security = {};
|
|
228
|
-
security['basic'] = [];
|
|
229
|
-
context.swagger.securityDefinitions['basic'] = { type: 'basic' };
|
|
230
|
-
} else if (header.value.match(/^Bearer /)) {
|
|
231
|
-
if (!security) security = {};
|
|
232
|
-
if (context.options.bearerAsApikey) {
|
|
233
|
-
security['bearer'] = [];
|
|
234
|
-
context.swagger.securityDefinitions['bearer'] = {
|
|
235
|
-
type: 'apiKey', in: 'header', name: 'Authorization'
|
|
236
|
-
};
|
|
237
|
-
} else {
|
|
238
|
-
security['oauth2'] = [];
|
|
239
|
-
context.swagger.securityDefinitions['oauth2'] = {
|
|
240
|
-
type: 'oauth2', flow: 'accessCode',
|
|
241
|
-
authorizationUrl: '', tokenUrl: '', scopes: {}
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
return security;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function swaggerParameters(parameters, uriTemplate) {
|
|
250
|
-
var PARAM_TYPES = {
|
|
251
|
-
'string': 'string',
|
|
252
|
-
'number': 'number',
|
|
253
|
-
'integer': 'integer',
|
|
254
|
-
'boolean': 'boolean', 'bool': 'boolean',
|
|
255
|
-
'array': 'array',
|
|
256
|
-
'file': 'file'
|
|
257
|
-
}
|
|
258
|
-
var params = [];
|
|
259
|
-
//console.log(parameters);
|
|
260
|
-
for (var l = 0; l < parameters.length; l++) {
|
|
261
|
-
var parameter = parameters[l];
|
|
262
|
-
//console.log(parameter);
|
|
263
|
-
// in = ["query", "header", "path", "formData", "body"]
|
|
264
|
-
var param = {
|
|
265
|
-
'name': parameter.name,
|
|
266
|
-
'in': getParamType(parameter.name, uriTemplate),
|
|
267
|
-
'description': parameter.description,
|
|
268
|
-
'required': parameter.required
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
if (parameter.example) {
|
|
272
|
-
param['x-example'] = parameter.example
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
var paramType = undefined;
|
|
276
|
-
if (PARAM_TYPES.hasOwnProperty(parameter.type)) {
|
|
277
|
-
paramType = PARAM_TYPES[parameter.type];
|
|
278
|
-
} else {
|
|
279
|
-
paramType = 'string';
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
var allowedValues = []
|
|
283
|
-
for (var j = 0; j < parameter.values.length; j++) {
|
|
284
|
-
allowedValues.push(parameter.values[j].value);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
var parameterDefault = undefined;
|
|
288
|
-
if (parameter.default) {
|
|
289
|
-
if (paramType === 'number' || paramType === 'integer') {
|
|
290
|
-
parameterDefault = Number(parameter.default);
|
|
291
|
-
} else {
|
|
292
|
-
parameterDefault = parameter.default;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Body parameters has all info in schema
|
|
297
|
-
if (param.in === 'body') {
|
|
298
|
-
param.schema = {
|
|
299
|
-
'type': paramType
|
|
300
|
-
}
|
|
301
|
-
if (allowedValues.length > 0) {
|
|
302
|
-
param.schema.enum = allowedValues;
|
|
303
|
-
}
|
|
304
|
-
if (parameterDefault) {
|
|
305
|
-
param.schema.default = parameterDefault;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
param.type = paramType;
|
|
310
|
-
if (parameterDefault) {
|
|
311
|
-
param.default = parameterDefault;
|
|
312
|
-
}
|
|
313
|
-
if (allowedValues.length > 0) {
|
|
314
|
-
param.enum = allowedValues;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
params.push(param);
|
|
318
|
-
}
|
|
319
|
-
return params;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
var searchDataStructure = function (contents) {
|
|
323
|
-
for (var i = 0; i < contents.length; i++) {
|
|
324
|
-
var content = contents[i];
|
|
325
|
-
if (content.element !== "dataStructure") continue;
|
|
326
|
-
return jsonSchemaFromMSON(content);
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
function generateSchemaFromExample(headers, example) {
|
|
331
|
-
if (!headers || !headers.some(header => (
|
|
332
|
-
header.name === 'Content-Type' && header.value.match(/application\/.*json/)
|
|
333
|
-
))) {
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
try {
|
|
337
|
-
const body = JSON.parse(example);
|
|
338
|
-
const scheme = GenerateSchema.json("", body);
|
|
339
|
-
if (!scheme) {
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
delete scheme.title;
|
|
343
|
-
delete scheme.$schema;
|
|
344
|
-
// if we have example values in the body then insert them into the json schema
|
|
345
|
-
if (scheme['type'] === 'object') {
|
|
346
|
-
scheme.example = body;
|
|
347
|
-
} else if (scheme['type'] === 'array') {
|
|
348
|
-
scheme.items.example = body;
|
|
349
|
-
}
|
|
350
|
-
return scheme;
|
|
351
|
-
} catch (e) {
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function getParamType(name, uriTemplate) {
|
|
357
|
-
if (!uriTemplate) return 'body';
|
|
358
|
-
for (var i = 0; i < uriTemplate.expressions.length; i++) {
|
|
359
|
-
var exp = uriTemplate.expressions[i];
|
|
360
|
-
if (!exp.varspecs) continue;
|
|
361
|
-
for (var j = 0; j < exp.varspecs.length; j++) {
|
|
362
|
-
var spec = exp.varspecs[j];
|
|
363
|
-
if (spec.varname === name) {
|
|
364
|
-
return exp.operator.symbol === '?' ? 'query' : 'path';
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
return 'body'; // TODO: decide 'header', 'formData', 'body'
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function fixArraySchema(schema) {
|
|
372
|
-
if (schema.type === 'array') {
|
|
373
|
-
if (!schema.hasOwnProperty('items')) schema.items = {};
|
|
374
|
-
} else if (schema.type === 'object') {
|
|
375
|
-
for (var k in schema.properties) fixArraySchema(schema.properties[k]);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function swaggerResponses(examples, options) {
|
|
380
|
-
var responses = {};
|
|
381
|
-
//console.log(examples);
|
|
382
|
-
for (var l = 0; l < examples.length; l++) {
|
|
383
|
-
var example = examples[l];
|
|
384
|
-
//console.log(example);
|
|
385
|
-
for (var m = 0; m < example.responses.length; m++) {
|
|
386
|
-
var response = example.responses[m];
|
|
387
|
-
//console.log(response);
|
|
388
|
-
var swaggerResponse = {
|
|
389
|
-
"description": response.description || http.STATUS_CODES[response.name],
|
|
390
|
-
"headers": {},
|
|
391
|
-
"examples": {}
|
|
392
|
-
};
|
|
393
|
-
if (options.preferReference) { // MSON then schema
|
|
394
|
-
var schema = searchDataStructure(response.content); // Attributes in response
|
|
395
|
-
if (schema) {
|
|
396
|
-
swaggerResponse.schema = schema;
|
|
397
|
-
} else if (response.reference) {
|
|
398
|
-
swaggerResponse.schema = {
|
|
399
|
-
'$ref': '#/definitions/' + escapeJSONPointer(response.reference.id + 'Model')
|
|
400
|
-
};
|
|
401
|
-
} else if (response.schema) {
|
|
402
|
-
try {
|
|
403
|
-
swaggerResponse.schema = JSON.parse(response.schema);
|
|
404
|
-
delete swaggerResponse.schema['$schema'];
|
|
405
|
-
fixArraySchema(swaggerResponse.schema); // work around for Swagger UI / Editor
|
|
406
|
-
} catch (e) { }
|
|
407
|
-
}
|
|
408
|
-
} else { // schema then MSON
|
|
409
|
-
if (response.schema) {
|
|
410
|
-
try {
|
|
411
|
-
swaggerResponse.schema = JSON.parse(response.schema);
|
|
412
|
-
delete swaggerResponse.schema['$schema'];
|
|
413
|
-
fixArraySchema(swaggerResponse.schema); // work around for Swagger UI / Editor
|
|
414
|
-
} catch (e) { }
|
|
415
|
-
}
|
|
416
|
-
if (!swaggerResponse.schema) {
|
|
417
|
-
var schema = searchDataStructure(response.content); // Attributes in response
|
|
418
|
-
if (schema) swaggerResponse.schema = schema;
|
|
419
|
-
}
|
|
420
|
-
if (!swaggerResponse.schema && response.reference) {
|
|
421
|
-
swaggerResponse.schema = {
|
|
422
|
-
'$ref': '#/definitions/' + escapeJSONPointer(response.reference.id + 'Model')
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
if (!swaggerResponse.schema) {
|
|
427
|
-
// fall back to body
|
|
428
|
-
// if (response.body) {
|
|
429
|
-
// schema = GenerateSchema.json("", JSON.parse(response.body));
|
|
430
|
-
// if (schema) {
|
|
431
|
-
// delete schema.title;
|
|
432
|
-
// delete schema.$schema;
|
|
433
|
-
// swaggerResponse.schema = schema;
|
|
434
|
-
// }
|
|
435
|
-
// }
|
|
436
|
-
|
|
437
|
-
// use object
|
|
438
|
-
// if (response.body) {
|
|
439
|
-
// swaggerResponse.schema = {type: "object"};
|
|
440
|
-
// }
|
|
441
|
-
}
|
|
442
|
-
for (var n = 0; n < response.headers.length; n++) {
|
|
443
|
-
var header = response.headers[n];
|
|
444
|
-
if (header.name.toLowerCase() === 'content-type') {
|
|
445
|
-
if (header.value.match(/application\/.*json/)) {
|
|
446
|
-
try {
|
|
447
|
-
swaggerResponse.examples[header.value] = JSON.parse(response.body);
|
|
448
|
-
//swaggerResponse.schema = {type: "object"};
|
|
449
|
-
} catch (e) { }
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
swaggerResponse.examples[header.value] = response.body;
|
|
453
|
-
} else if (header.name.toLowerCase() !== 'authorization') {
|
|
454
|
-
swaggerResponse.headers[header.name] = { 'type': 'string' }
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
responses[response.name] = swaggerResponse;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
return responses;
|
|
180
|
+
return processRequests(operation, action, context)
|
|
461
181
|
}
|
|
462
182
|
|
|
463
183
|
exports.noconvert = function (data, callback) {
|
|
@@ -476,10 +196,6 @@ exports.convert = function (data, options, callback) {
|
|
|
476
196
|
}
|
|
477
197
|
try {
|
|
478
198
|
var result = drafter.parse(data, { type: 'ast' });
|
|
479
|
-
//for (var i = 0; i < result.warnings.length; i++) {
|
|
480
|
-
// var warn = result.warnings[i];
|
|
481
|
-
// console.log(warn);
|
|
482
|
-
//}
|
|
483
199
|
var swagger = apib2swagger(result.ast, options);
|
|
484
200
|
return callback(null, { swagger: swagger });
|
|
485
201
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apib2swagger",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Convert API Blueprint to Swagger.",
|
|
5
5
|
"bin": "./bin/apib2swagger.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,15 +20,17 @@
|
|
|
20
20
|
"apib-include-directive": "^0.1.0",
|
|
21
21
|
"drafter.js": "^2.6.2",
|
|
22
22
|
"generate-schema": "^2.5.0",
|
|
23
|
-
"js-yaml": "^
|
|
23
|
+
"js-yaml": "^4.1.0",
|
|
24
|
+
"lodash.isequal": "^4.5.0",
|
|
24
25
|
"nopt": "^3.0.6",
|
|
25
26
|
"uritemplate": "^0.3.4"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
|
-
"coveralls": "^3.
|
|
29
|
+
"coveralls": "^3.1.1",
|
|
29
30
|
"cross-env": "^5.2.0",
|
|
30
31
|
"mocha": "^5.2.0",
|
|
31
|
-
"nyc": "^13.0
|
|
32
|
+
"nyc": "^13.3.0",
|
|
33
|
+
"sinon": "^12.0.1",
|
|
32
34
|
"tv4": "^1.2.7"
|
|
33
35
|
}
|
|
34
36
|
}
|
|
File without changes
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
var escapeJSONPointer = require('./escape_json_pointer');
|
|
2
2
|
|
|
3
|
-
function convertMsonToJsonSchema(content) {
|
|
3
|
+
function convertMsonToJsonSchema(content, options) {
|
|
4
4
|
// for apib._version = "4.0"
|
|
5
5
|
var mson = content.content[0];
|
|
6
|
-
var schema = convert(mson);
|
|
6
|
+
var schema = convert(mson, options);
|
|
7
7
|
if (schema.type === 'array') {
|
|
8
8
|
var fixedType = false;
|
|
9
9
|
if (mson.attributes && mson.attributes.typeAttributes) {
|
|
@@ -18,16 +18,18 @@ function convertMsonToJsonSchema(content) {
|
|
|
18
18
|
return schema;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function convert(mson) {
|
|
21
|
+
function convert(mson, options) {
|
|
22
|
+
const { openApi3 } = options
|
|
23
|
+
const componentsPath = openApi3 ? '#/components/schemas/' : '#/definitions/'
|
|
22
24
|
// mson.element = "boolean", "string", "number", "array", "enum", "object", CustomType
|
|
23
25
|
switch (mson.element) {
|
|
24
26
|
case 'array':
|
|
25
27
|
if (!mson.content || mson.content.length === 0) {
|
|
26
28
|
return { type: 'array', items: {} };
|
|
27
29
|
} else if (mson.content.length === 1) {
|
|
28
|
-
return { type: 'array', items: convert(mson.content[0]) };
|
|
30
|
+
return { type: 'array', items: convert(mson.content[0], options) };
|
|
29
31
|
} else if (mson.content.length > 1) {
|
|
30
|
-
return { type: 'array', items: { 'anyOf': mson.content.map(convert) } };
|
|
32
|
+
return { type: 'array', items: { 'anyOf': mson.content.map((m) => convert(m, options)) } };
|
|
31
33
|
}
|
|
32
34
|
case 'enum':
|
|
33
35
|
return convertEnum(mson.content);
|
|
@@ -39,7 +41,7 @@ function convert(mson) {
|
|
|
39
41
|
return { type: mson.element };
|
|
40
42
|
default:
|
|
41
43
|
if (!mson.content) {
|
|
42
|
-
return { '$ref':
|
|
44
|
+
return { '$ref': componentsPath + escapeJSONPointer(mson.element) };
|
|
43
45
|
}
|
|
44
46
|
break;
|
|
45
47
|
}
|
|
@@ -51,7 +53,7 @@ function convert(mson) {
|
|
|
51
53
|
for (var j = 0; mson.content && j < mson.content.length; j++) {
|
|
52
54
|
var member = mson.content[j];
|
|
53
55
|
if (member.element !== "member") continue;
|
|
54
|
-
schema.properties[member.content.key.content] = convert(member.content.value);
|
|
56
|
+
schema.properties[member.content.key.content] = convert(member.content.value, options);
|
|
55
57
|
if (member.meta && member.meta.description) {
|
|
56
58
|
schema.properties[member.content.key.content].description = member.meta.description;
|
|
57
59
|
}
|
|
@@ -65,6 +67,9 @@ function convert(mson) {
|
|
|
65
67
|
case 'required':
|
|
66
68
|
schema.required.push(member.content.key.content);
|
|
67
69
|
break;
|
|
70
|
+
case 'nullable':
|
|
71
|
+
schema.properties[member.content.key.content].type = [schema.properties[member.content.key.content].type, 'null']
|
|
72
|
+
break;
|
|
68
73
|
}
|
|
69
74
|
});
|
|
70
75
|
}
|
|
@@ -79,7 +84,7 @@ function convert(mson) {
|
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
if (mson.element !== 'object') {
|
|
82
|
-
return { 'allOf': [{ '$ref':
|
|
87
|
+
return { 'allOf': [{ '$ref': componentsPath + escapeJSONPointer(mson.element) }, schema] };
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
return schema;
|