spice-js 2.7.35 → 2.7.36
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/build/docs/generator.js +104 -12
- package/package.json +1 -1
- package/src/docs/generator.js +119 -14
package/build/docs/generator.js
CHANGED
|
@@ -19,6 +19,50 @@ function getPackageInfo() {
|
|
|
19
19
|
var pac = require(_path2.default.join(spice.parent_app_dir, "/package.json"));
|
|
20
20
|
return pac;
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
// Swagger 2.0 restricts a parameter to these types. The schema vocabulary is
|
|
24
|
+
// richer ("date", "Array", model names) and anything outside this set makes the
|
|
25
|
+
// parameter invalid, so Swagger UI renders no input for it at all.
|
|
26
|
+
var SWAGGER_PARAMETER_TYPES = ["string", "number", "integer", "boolean", "array", "file"];
|
|
27
|
+
function toParameterType(type) {
|
|
28
|
+
if (typeof type != "string") {
|
|
29
|
+
return {
|
|
30
|
+
type: "string"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
var lowered = type.toLowerCase();
|
|
34
|
+
if (lowered == "date") {
|
|
35
|
+
return {
|
|
36
|
+
type: "string",
|
|
37
|
+
format: "date"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (lowered == "datetime") {
|
|
41
|
+
return {
|
|
42
|
+
type: "string",
|
|
43
|
+
format: "date-time"
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Objects and collections are submitted as text in a form body
|
|
47
|
+
if (_lodash.default.includes(["object", "array"], lowered)) {
|
|
48
|
+
return {
|
|
49
|
+
type: "string"
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (_lodash.default.includes(SWAGGER_PARAMETER_TYPES, lowered)) {
|
|
53
|
+
return {
|
|
54
|
+
type: lowered
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Model references and anything unrecognised
|
|
58
|
+
return {
|
|
59
|
+
type: "string"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function trimTrailingSlash(p) {
|
|
63
|
+
if (typeof p !== "string") return p;
|
|
64
|
+
return p.length > 1 ? p.replace(/\/+$/, "") : p;
|
|
65
|
+
}
|
|
22
66
|
function createPath(path) {
|
|
23
67
|
return _lodash.default.join(_lodash.default.map(_lodash.default.split(path, "/"), seg => {
|
|
24
68
|
if (_lodash.default.startsWith(seg, ":")) {
|
|
@@ -92,18 +136,35 @@ function getRoutes(models) {
|
|
|
92
136
|
description: "Not found"
|
|
93
137
|
}
|
|
94
138
|
};
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
139
|
+
|
|
140
|
+
// koa-router drops the trailing slash when it applies a router
|
|
141
|
+
// prefix, so a route declared as router.post("/") ends up with
|
|
142
|
+
// layer.path equal to the prefix itself and never "ends with" the
|
|
143
|
+
// documented "/". That left every collection-root endpoint without
|
|
144
|
+
// its comment, and so without parameters, description or response
|
|
145
|
+
// schema. Expand the documented path against the prefix and match
|
|
146
|
+
// exactly, keeping the old suffix match as a fallback for files
|
|
147
|
+
// that document fully-qualified paths.
|
|
148
|
+
var layer_method = _lodash.default.lowerCase(_lodash.default.last(layer.methods));
|
|
149
|
+
var layer_path = trimTrailingSlash(layer.path);
|
|
150
|
+
var router_prefix = router.opts && router.opts.prefix || "";
|
|
151
|
+
var candidates = _lodash.default.filter(comment_processed, item => item.method == layer_method);
|
|
152
|
+
var comment = _lodash.default.find(candidates, item => layer_path === trimTrailingSlash(router_prefix + item.original_path)) || _lodash.default.find(candidates, item => {
|
|
153
|
+
var documented = trimTrailingSlash(item.original_path);
|
|
154
|
+
// "/" would suffix-match every path in the file
|
|
155
|
+
if (documented === "/" || documented === "") return false;
|
|
156
|
+
return _lodash.default.endsWith(layer_path, documented);
|
|
102
157
|
});
|
|
103
158
|
if (comment != undefined) {
|
|
104
159
|
_path.description = comment.description;
|
|
105
160
|
_path.operationId = _uuid.default.v4();
|
|
106
161
|
_path.parameters = [...preparParameters(comment, undefined, models, router), ..._path.params];
|
|
162
|
+
// Swagger 2.0 requires an operation carrying formData parameters
|
|
163
|
+
// to declare a form media type, otherwise "Try it out" has no
|
|
164
|
+
// body to build and the fields are ignored.
|
|
165
|
+
if (_lodash.default.some(_path.parameters, param => param.in === "formData")) {
|
|
166
|
+
_path.consumes = [_lodash.default.some(_path.parameters, param => param.type === "file") ? "multipart/form-data" : "application/x-www-form-urlencoded"];
|
|
167
|
+
}
|
|
107
168
|
var doc_return = filterTags(comment, "returns");
|
|
108
169
|
if (doc_return.length > 0) {
|
|
109
170
|
if (doc_return[0].type == undefined) {
|
|
@@ -166,14 +227,14 @@ function preparParameters(comment, defaultModel, models) {
|
|
|
166
227
|
}
|
|
167
228
|
function modelToParameter(model) {
|
|
168
229
|
return _lodash.default.map(models[model], (value, key) => {
|
|
169
|
-
return {
|
|
170
|
-
name: key
|
|
171
|
-
|
|
230
|
+
return _extends({
|
|
231
|
+
name: key
|
|
232
|
+
}, toParameterType(value.type), {
|
|
172
233
|
description: value.description ? value.description : "",
|
|
173
234
|
required: value.required ? value.required : false,
|
|
174
235
|
paramType: "form",
|
|
175
236
|
in: "formData"
|
|
176
|
-
};
|
|
237
|
+
});
|
|
177
238
|
}).filter(obj => obj != undefined);
|
|
178
239
|
}
|
|
179
240
|
/* console.log("Props::::::: ", [
|
|
@@ -326,6 +387,37 @@ function generateTags() {
|
|
|
326
387
|
};
|
|
327
388
|
});
|
|
328
389
|
}
|
|
390
|
+
|
|
391
|
+
// An @returns tag names a model, but plenty of routes use it for prose
|
|
392
|
+
// ("success status", "Providers + models"). Those become $refs that point at a
|
|
393
|
+
// definition which does not exist, and Swagger UI cannot resolve them, so the
|
|
394
|
+
// response renders blank. Keep the prose as a description instead.
|
|
395
|
+
function pruneDanglingRefs(spec) {
|
|
396
|
+
var available = _lodash.default.keys(spec.definitions || {});
|
|
397
|
+
_lodash.default.each(spec.paths, methods => {
|
|
398
|
+
_lodash.default.each(methods, operation => {
|
|
399
|
+
var response = _lodash.default.get(operation, ["responses", "200"]);
|
|
400
|
+
if (response == undefined || response.schema == undefined) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
var schema = response.schema;
|
|
404
|
+
var holder = schema.$ref != undefined ? schema : _lodash.default.get(schema, ["items", "$ref"]) != undefined ? schema.items : _lodash.default.get(schema, ["additionalProperties", "$ref"]) != undefined ? schema.additionalProperties : undefined;
|
|
405
|
+
if (holder == undefined) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
var referenced = _lodash.default.last(_lodash.default.split(holder.$ref, "/"));
|
|
409
|
+
if (_lodash.default.includes(available, referenced)) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
delete holder.$ref;
|
|
413
|
+
holder.type = "object";
|
|
414
|
+
if (referenced) {
|
|
415
|
+
response.description = "Successful Operation - " + referenced;
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
return spec;
|
|
420
|
+
}
|
|
329
421
|
function generateSpec() {
|
|
330
422
|
return _generateSpec.apply(this, arguments);
|
|
331
423
|
}
|
|
@@ -366,7 +458,7 @@ function _generateSpec() {
|
|
|
366
458
|
paths: generatePaths(routes),
|
|
367
459
|
definitions: generateDefinitions(models)
|
|
368
460
|
};
|
|
369
|
-
return spec;
|
|
461
|
+
return pruneDanglingRefs(spec);
|
|
370
462
|
});
|
|
371
463
|
return _generateSpec.apply(this, arguments);
|
|
372
464
|
}
|
package/package.json
CHANGED
package/src/docs/generator.js
CHANGED
|
@@ -13,6 +13,45 @@ function getPackageInfo() {
|
|
|
13
13
|
return pac;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// Swagger 2.0 restricts a parameter to these types. The schema vocabulary is
|
|
17
|
+
// richer ("date", "Array", model names) and anything outside this set makes the
|
|
18
|
+
// parameter invalid, so Swagger UI renders no input for it at all.
|
|
19
|
+
const SWAGGER_PARAMETER_TYPES = [
|
|
20
|
+
"string",
|
|
21
|
+
"number",
|
|
22
|
+
"integer",
|
|
23
|
+
"boolean",
|
|
24
|
+
"array",
|
|
25
|
+
"file",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function toParameterType(type) {
|
|
29
|
+
if (typeof type != "string") {
|
|
30
|
+
return { type: "string" };
|
|
31
|
+
}
|
|
32
|
+
let lowered = type.toLowerCase();
|
|
33
|
+
if (lowered == "date") {
|
|
34
|
+
return { type: "string", format: "date" };
|
|
35
|
+
}
|
|
36
|
+
if (lowered == "datetime") {
|
|
37
|
+
return { type: "string", format: "date-time" };
|
|
38
|
+
}
|
|
39
|
+
// Objects and collections are submitted as text in a form body
|
|
40
|
+
if (_.includes(["object", "array"], lowered)) {
|
|
41
|
+
return { type: "string" };
|
|
42
|
+
}
|
|
43
|
+
if (_.includes(SWAGGER_PARAMETER_TYPES, lowered)) {
|
|
44
|
+
return { type: lowered };
|
|
45
|
+
}
|
|
46
|
+
// Model references and anything unrecognised
|
|
47
|
+
return { type: "string" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function trimTrailingSlash(p) {
|
|
51
|
+
if (typeof p !== "string") return p;
|
|
52
|
+
return p.length > 1 ? p.replace(/\/+$/, "") : p;
|
|
53
|
+
}
|
|
54
|
+
|
|
16
55
|
function createPath(path) {
|
|
17
56
|
return _.join(
|
|
18
57
|
_.map(_.split(path, "/"), (seg) => {
|
|
@@ -100,17 +139,36 @@ function getRoutes(models) {
|
|
|
100
139
|
},
|
|
101
140
|
};
|
|
102
141
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
142
|
+
// koa-router drops the trailing slash when it applies a router
|
|
143
|
+
// prefix, so a route declared as router.post("/") ends up with
|
|
144
|
+
// layer.path equal to the prefix itself and never "ends with" the
|
|
145
|
+
// documented "/". That left every collection-root endpoint without
|
|
146
|
+
// its comment, and so without parameters, description or response
|
|
147
|
+
// schema. Expand the documented path against the prefix and match
|
|
148
|
+
// exactly, keeping the old suffix match as a fallback for files
|
|
149
|
+
// that document fully-qualified paths.
|
|
150
|
+
let layer_method = _.lowerCase(_.last(layer.methods));
|
|
151
|
+
let layer_path = trimTrailingSlash(layer.path);
|
|
152
|
+
let router_prefix = (router.opts && router.opts.prefix) || "";
|
|
153
|
+
|
|
154
|
+
let candidates = _.filter(
|
|
155
|
+
comment_processed,
|
|
156
|
+
(item) => item.method == layer_method
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
let comment =
|
|
160
|
+
_.find(
|
|
161
|
+
candidates,
|
|
162
|
+
(item) =>
|
|
163
|
+
layer_path ===
|
|
164
|
+
trimTrailingSlash(router_prefix + item.original_path)
|
|
165
|
+
) ||
|
|
166
|
+
_.find(candidates, (item) => {
|
|
167
|
+
let documented = trimTrailingSlash(item.original_path);
|
|
168
|
+
// "/" would suffix-match every path in the file
|
|
169
|
+
if (documented === "/" || documented === "") return false;
|
|
170
|
+
return _.endsWith(layer_path, documented);
|
|
171
|
+
});
|
|
114
172
|
|
|
115
173
|
if (comment != undefined) {
|
|
116
174
|
path.description = comment.description;
|
|
@@ -119,6 +177,18 @@ function getRoutes(models) {
|
|
|
119
177
|
...preparParameters(comment, undefined, models, router),
|
|
120
178
|
...path.params,
|
|
121
179
|
];
|
|
180
|
+
// Swagger 2.0 requires an operation carrying formData parameters
|
|
181
|
+
// to declare a form media type, otherwise "Try it out" has no
|
|
182
|
+
// body to build and the fields are ignored.
|
|
183
|
+
if (_.some(path.parameters, (param) => param.in === "formData")) {
|
|
184
|
+
path.consumes = [
|
|
185
|
+
(
|
|
186
|
+
_.some(path.parameters, (param) => param.type === "file")
|
|
187
|
+
) ?
|
|
188
|
+
"multipart/form-data"
|
|
189
|
+
: "application/x-www-form-urlencoded",
|
|
190
|
+
];
|
|
191
|
+
}
|
|
122
192
|
let doc_return = filterTags(comment, "returns");
|
|
123
193
|
if (doc_return.length > 0) {
|
|
124
194
|
if (doc_return[0].type == undefined) {
|
|
@@ -185,8 +255,7 @@ function preparParameters(comment, defaultModel, models) {
|
|
|
185
255
|
return _.map(models[model], (value, key) => {
|
|
186
256
|
return {
|
|
187
257
|
name: key,
|
|
188
|
-
type
|
|
189
|
-
_.includes(["object", "array"], value.type) ? "string" : value.type,
|
|
258
|
+
...toParameterType(value.type),
|
|
190
259
|
description: value.description ? value.description : "",
|
|
191
260
|
required: value.required ? value.required : false,
|
|
192
261
|
paramType: "form",
|
|
@@ -353,6 +422,42 @@ function generateTags() {
|
|
|
353
422
|
});
|
|
354
423
|
}
|
|
355
424
|
|
|
425
|
+
// An @returns tag names a model, but plenty of routes use it for prose
|
|
426
|
+
// ("success status", "Providers + models"). Those become $refs that point at a
|
|
427
|
+
// definition which does not exist, and Swagger UI cannot resolve them, so the
|
|
428
|
+
// response renders blank. Keep the prose as a description instead.
|
|
429
|
+
function pruneDanglingRefs(spec) {
|
|
430
|
+
let available = _.keys(spec.definitions || {});
|
|
431
|
+
_.each(spec.paths, (methods) => {
|
|
432
|
+
_.each(methods, (operation) => {
|
|
433
|
+
let response = _.get(operation, ["responses", "200"]);
|
|
434
|
+
if (response == undefined || response.schema == undefined) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
let schema = response.schema;
|
|
438
|
+
let holder =
|
|
439
|
+
schema.$ref != undefined ? schema
|
|
440
|
+
: _.get(schema, ["items", "$ref"]) != undefined ? schema.items
|
|
441
|
+
: _.get(schema, ["additionalProperties", "$ref"]) != undefined ?
|
|
442
|
+
schema.additionalProperties
|
|
443
|
+
: undefined;
|
|
444
|
+
if (holder == undefined) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
let referenced = _.last(_.split(holder.$ref, "/"));
|
|
448
|
+
if (_.includes(available, referenced)) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
delete holder.$ref;
|
|
452
|
+
holder.type = "object";
|
|
453
|
+
if (referenced) {
|
|
454
|
+
response.description = `Successful Operation - ${referenced}`;
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
return spec;
|
|
459
|
+
}
|
|
460
|
+
|
|
356
461
|
export default async function generateSpec() {
|
|
357
462
|
let { description, version, name } = getPackageInfo();
|
|
358
463
|
let models = _.cloneDeep(await getModels());
|
|
@@ -392,5 +497,5 @@ export default async function generateSpec() {
|
|
|
392
497
|
paths: generatePaths(routes),
|
|
393
498
|
definitions: generateDefinitions(models),
|
|
394
499
|
};
|
|
395
|
-
return spec;
|
|
500
|
+
return pruneDanglingRefs(spec);
|
|
396
501
|
}
|