Skip to content

Commit 534af7f

Browse files
danielsharveytheoomoregbee
authored andcommitted
fix: Improve correctness of generated OpenAPI 3 output
Uploaded to https://redoc.ly/developer-portal yielding these issues and fixes. Refactor '$ref' checks using new common code. Fix cross-generated-route data pollution by ensuring source swagger data cloned. Add underscore to path variable names, used to differentiate the PK value used for paths from query variables. Specifically, differentiate the PK value used for shortcut blueprint update routes, which allow for PK update using query parameters. Some validators expect unique names across all parameter types. Sort tag name specified as part of Operation. Update test data and test fixtures.
1 parent c389d10 commit 534af7f

File tree

6 files changed

+3085
-309
lines changed

6 files changed

+3085
-309
lines changed

api/controllers/UserController.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ module.exports = {
4444
* parameters:
4545
* - name: example-only
4646
* description: Username to use for logout (dummy for test)
47-
* in: path
47+
* in: query
4848
* required: true
4949
* schema:
5050
* type: string

lib/generators.ts

+15-19
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,12 @@ export const generatePaths = (routes: SwaggerRouteInfo[], templates: BlueprintAc
353353

354354
let pathEntry: OpenApi.Operation;
355355

356+
const isParam = (inType: string, name: string): boolean => {
357+
return !!pathEntry.parameters
358+
.map(parameter => resolveRef(specification, parameter) as OpenApi.Parameter)
359+
.find(parameter => parameter && 'in' in parameter && parameter.in == inType && parameter.name == name);
360+
}
361+
356362
if (route.middlewareType === MiddlewareType.BLUEPRINT && route.model) {
357363

358364
if(route.model.swagger?.modelSchema?.exclude === true) {
@@ -373,7 +379,7 @@ export const generatePaths = (routes: SwaggerRouteInfo[], templates: BlueprintAc
373379
if (components.parameters && !components.parameters[pname]) {
374380
components.parameters[pname] = {
375381
in: 'path',
376-
name: primaryKey,
382+
name: '_' + primaryKey, // note '_' as per transformSailsPathsToSwaggerPaths()
377383
required: true,
378384
schema: generateAttributeSchema(attributeInfo),
379385
description: subst('The desired **{globalId}** record\'s primary key value'),
@@ -391,12 +397,10 @@ export const generatePaths = (routes: SwaggerRouteInfo[], templates: BlueprintAc
391397
tags: route.swagger?.tags || route.model.swagger.modelSchema?.tags || route.model.swagger.actions?.allactions?.tags || [route.model.globalId],
392398
parameters,
393399
responses: cloneDeep(defaultsValues.responses || {}),
394-
...omit(route.model.swagger.actions?.allactions || {}, 'exclude'),
395-
...omit(route.swagger || {}, 'exclude')
400+
...cloneDeep(omit(route.model.swagger.actions?.allactions || {}, 'exclude')),
401+
...cloneDeep(omit(route.swagger || {}, 'exclude')),
396402
};
397403

398-
const isParam = (inType: string, name: string): boolean => !!pathEntry.parameters.find(parameter => 'in' in parameter && parameter.in == inType && parameter.name == name);
399-
400404
const modifiers = {
401405

402406
addPopulateQueryParam: () => {
@@ -625,7 +629,7 @@ export const generatePaths = (routes: SwaggerRouteInfo[], templates: BlueprintAc
625629
pathEntry = {
626630
parameters: [],
627631
responses: cloneDeep(defaultsValues.responses || {}),
628-
...omit(route.swagger || {}, 'exclude')
632+
...cloneDeep(omit(route.swagger || {}, 'exclude')),
629633
}
630634

631635
if (route.actionType === 'actions2') {
@@ -761,21 +765,9 @@ export const generatePaths = (routes: SwaggerRouteInfo[], templates: BlueprintAc
761765
}
762766

763767
if (route.variables) {
764-
// first resolve '$ref' parameters
765-
const resolved = pathEntry.parameters.map(parameter => {
766-
let ref = '$ref' in parameter && parameter.$ref;
767-
if (!ref) return parameter;
768-
const _prefix = '#/components/parameters/';
769-
if (ref.startsWith(_prefix)) {
770-
ref = ref.substring(_prefix.length);
771-
const dereferenced = components.parameters![ref];
772-
return dereferenced ? dereferenced : parameter;
773-
}
774-
}) as OpenApi.Parameter[];
775-
776768
// now add patternVariables that don't already exist
777769
route.variables.map(v => {
778-
const existing = resolved.find(p => p && 'in' in p && p.in == 'path' && p.name == v);
770+
const existing = isParam('path', v);
779771
if (existing) return;
780772
pathEntry.parameters.push({
781773
in: 'path',
@@ -787,6 +779,10 @@ export const generatePaths = (routes: SwaggerRouteInfo[], templates: BlueprintAc
787779
});
788780
}
789781

782+
if(pathEntry.tags) {
783+
pathEntry.tags.sort();
784+
}
785+
790786
set(paths, [route.path, route.verb], pathEntry);
791787
});
792788

lib/transformations.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,26 @@ const transformSailsPathToSwaggerPath = (path: string): string => {
1616
* Maps from a Sails route path of the form `/path/:id` to a
1717
* Swagger path of the form `/path/{id}`.
1818
*
19-
* Also transform standard Sails '{id}' to '{primaryKeyAttributeName}' where
20-
* primary key is not `id`.
19+
* Also transform standard Sails primary key reference '{id}' to
20+
* '_{primaryKeyAttributeName}'.
21+
*
22+
* Add underscore to path variable names, used to differentiate the PK value
23+
* used for paths from query variables. Specifically, differentiate the PK value
24+
* used for shortcut blueprint update routes, which allow for PK update
25+
* using query parameters. Some validators expect unique names across all
26+
* parameter types.
2127
*/
2228
export const transformSailsPathsToSwaggerPaths = (routes: SwaggerRouteInfo[]): void => {
2329

2430
routes.map(route => {
2531

2632
route.path = transformSailsPathToSwaggerPath(route.path);
2733

28-
if (route.model?.primaryKey && route.model.primaryKey !== 'id') {
29-
route.path = route.path.replace('{id}', `{${route.model.primaryKey}}`);
30-
route.variables = route.variables.map(v => v === 'id' ? route.model!.primaryKey : v);
31-
route.optionalVariables = route.optionalVariables.map(v => v === 'id' ? route.model!.primaryKey : v);
34+
if (route.model?.primaryKey) {
35+
const pathVariable = '_' + route.model.primaryKey;
36+
route.path = route.path.replace('{id}', `{${pathVariable}}`);
37+
route.variables = route.variables.map(v => v === 'id' ? pathVariable : v);
38+
route.optionalVariables = route.optionalVariables.map(v => v === 'id' ? pathVariable : v);
3239
}
3340

3441
});
@@ -123,7 +130,7 @@ export const transformSailsPathsToSwaggerPaths = (routes: SwaggerRouteInfo[]): v
123130
// first route becomes 'aggregated' version
124131
const g = actionGroup[0];
125132
const prefix = g.match[1];
126-
const pk = g.route.model!.primaryKey;
133+
const pk = '_' + g.route.model!.primaryKey; // note '_' as per transformSailsPathsToSwaggerPaths()
127134
const shortcutRoutePart = g.match[3] || '';
128135
const childPart = g.match[4] || '';
129136
const aggregatedRoute = {

0 commit comments

Comments
 (0)