Skip to content

Commit

Permalink
feat: flag definitions that are defined but never used (#144)
Browse files Browse the repository at this point in the history
adds checks for parameters, responses, and all other components object types defined in the components section that are not used in the API definition
  • Loading branch information
barrett-schonefeld committed Feb 21, 2020
1 parent 628d6c0 commit a131408
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 15 deletions.
63 changes: 50 additions & 13 deletions src/plugins/validation/2and3/semantic-validators/refs.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@ module.exports.validate = function({ jsSpec, specStr, isOAS3 }) {
return messages;
}

const basePath = isOAS3 ? ['components', 'schemas'] : ['definitions'];

// Assertation 1
// This is a "creative" way to approach the problem of collecting used $refs,
// but other solutions required walking the jsSpec recursively to detect $refs,
// which can be quite slow.
// Collects all refs in the API definition
const refRegex = /\$ref.*["'](.*)["']/g;
let match = refRegex.exec(specStr);
const refs = [];
Expand All @@ -29,21 +24,63 @@ module.exports.validate = function({ jsSpec, specStr, isOAS3 }) {
match = refRegex.exec(specStr);
}

const definitionSectionName = isOAS3 ? 'components' : 'definitions';
// de-dupe the array, and filter out non-definition refs
const definitionsRefs = filter(uniq(refs), v =>
startsWith(v, `#/${basePath.join('/')}`)
startsWith(v, `#/${definitionSectionName}`)
);

const definitions = isOAS3 ? jsSpec.components.schemas : jsSpec.definitions;
each(definitions, (def, defName) => {
if (definitionsRefs.indexOf(`#/${basePath.join('/')}/${defName}`) === -1) {
// checks if the definitions are used, and if not, record a warning
if (isOAS3) {
// securitySchemes definition type excluded because
// security-definitions-ibm.js checks for unused security schemes
const definitionTypeList = [
'schemas',
'parameters',
'responses',
'examples',
'requestBodies',
'headers',
'links',
'callbacks'
];
definitionTypeList.forEach(function(definitionType) {
if (jsSpec.components && jsSpec.components[definitionType]) {
recordDefinitionsNotUsed(
jsSpec.components[definitionType],
definitionsRefs,
['components', definitionType],
messages
);
}
});
} else {
if (jsSpec.definitions) {
recordDefinitionsNotUsed(
jsSpec.definitions,
definitionsRefs,
['definitions'],
messages
);
}
}

return messages;
};

function recordDefinitionsNotUsed(
definitions,
definitionsRefs,
basePath,
messages
) {
each(definitions, (_, defName) => {
if (!definitionsRefs.includes(`#/${basePath.join('/')}/${defName}`)) {
messages.addMessage(
`${basePath.join('.')}.${defName}`,
'Definition was declared but never used in document',
'warning'
);
}
});

return messages;
};
}
170 changes: 168 additions & 2 deletions test/plugins/validation/2and3/refs.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('validation plugin - semantic - refs', function() {
);
});

it('should warn about an unused definition - OpenAPI 3', function() {
it('should warn about an unused schema definition - OpenAPI 3', function() {
const jsSpec = {
paths: {
'/CoolPath/{id}': {
Expand Down Expand Up @@ -83,7 +83,7 @@ describe('validation plugin - semantic - refs', function() {
);
});

it('should not warn about a used definition - OpenAPI 3', function() {
it('should not warn about a used schema definition - OpenAPI 3', function() {
const jsSpec = {
paths: {
'/CoolPath/{id}': {
Expand Down Expand Up @@ -124,4 +124,170 @@ describe('validation plugin - semantic - refs', function() {
expect(res.warnings.length).toEqual(0);
});
});

it('should warn about an unused parameter definition - OpenAPI 3', function() {
const jsSpec = {
paths: {
'/CoolPath/{id}': {
parameters: [
{
$ref: '#/components/parameters/one_parameter_definition'
}
]
}
},
components: {
parameters: {
one_parameter_definition: {
name: 'id',
in: 'path',
description: 'An id'
},
other_parameter_definition: {
name: 'other',
in: 'query',
description: 'another param'
}
}
}
};

const specStr = JSON.stringify(jsSpec, null, 2);
const isOAS3 = true;

const res = validate({ jsSpec, specStr, isOAS3 });
expect(res.warnings[0].path).toEqual(
'components.parameters.other_parameter_definition'
);
expect(res.warnings[0].message).toEqual(
'Definition was declared but never used in document'
);
expect(res.warnings.length).toEqual(1);
});

it('should not warn about used parameter definitions - OpenAPI 3', function() {
const jsSpec = {
paths: {
'/CoolPath/{id}': {
parameters: [
{
$ref: '#/components/parameters/one_parameter_definition'
},
{
$ref: '#/components/parameters/other_parameter_definition'
}
]
}
},
components: {
parameters: {
one_parameter_definition: {
name: 'id',
in: 'path',
description: 'An id'
},
other_parameter_definition: {
name: 'other',
in: 'query',
description: 'another param'
}
}
}
};

const specStr = JSON.stringify(jsSpec, null, 2);
const isOAS3 = true;

const res = validate({ jsSpec, specStr, isOAS3 });
expect(res.warnings.length).toEqual(0);
});

it('should warn about an unused response definition - OpenAPI 3', function() {
const jsSpec = {
paths: {
'/CoolPath/{id}': {
responses: {
'200': {
description: '200 response',
content: {
'application/json': {
schema: {
type: 'string'
}
}
}
}
}
}
},
components: {
responses: {
response400: {
description: '400 response',
content: {
'application/json': {
schema: {
type: 'string'
}
}
}
}
}
}
};

const specStr = JSON.stringify(jsSpec, null, 2);
const isOAS3 = true;

const res = validate({ jsSpec, specStr, isOAS3 });
expect(res.warnings[0].path).toEqual('components.responses.response400');
expect(res.warnings[0].message).toEqual(
'Definition was declared but never used in document'
);
expect(res.warnings.length).toEqual(1);
});

it('should not warn about a used response definition - OpenAPI 3', function() {
const jsSpec = {
paths: {
'/CoolPath/{id}': {
responses: {
'200': {
description: '200 response',
content: {
'application/json': {
schema: {
type: 'string'
}
}
}
},
'400': {
$ref: '#/components/responses/response400'
}
}
}
},
components: {
responses: {
response400: {
description: '400 response',
content: {
'application/json': {
schema: {
type: 'string'
}
}
}
}
}
}
};

const specStr = JSON.stringify(jsSpec, null, 2);
const isOAS3 = true;

const res = validate({ jsSpec, specStr, isOAS3 });
expect(res.warnings.length).toEqual(0);
});
});

0 comments on commit a131408

Please sign in to comment.