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