Skip to content

Commit

Permalink
feat: initial Spectral integration
Browse files Browse the repository at this point in the history
  • Loading branch information
jorge-ibm committed Sep 9, 2020
1 parent 3c13c47 commit ee2201f
Show file tree
Hide file tree
Showing 16 changed files with 3,106 additions and 106 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ dist
bin/
.nyc_output/
coverage/
.spectral.json
1,012 changes: 910 additions & 102 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"pkg": "./node_modules/.bin/pkg --out-path=./bin ./package.json; cd bin; rename -f 's/ibm-openapi-validator-(linux|macos|win)/lint-openapi-$1/g' ./ibm-openapi-*"
},
"dependencies": {
"@stoplight/spectral": "^5.5.0",
"chalk": "^2.4.1",
"commander": "^2.17.1",
"deepmerge": "^2.1.1",
Expand Down
27 changes: 25 additions & 2 deletions src/cli-validator/runValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ const print = require('./utils/printResults');
const printJson = require('./utils/printJsonResults');
const printError = require('./utils/printError');
const preprocessFile = require('./utils/preprocessFile');
const { Spectral, isOpenApiv2, isOpenApiv3 } = require('@stoplight/spectral');

// default spectral ruleset file
const defaultSpectralRuleset =
process.cwd() + '/src/spectral/rulesets/.defaultsForSpectral.json';

// import the init module for creating a .validaterc file
const init = require('./utils/init.js');
Expand Down Expand Up @@ -49,6 +54,11 @@ const processInput = async function(program) {

const chalk = new chalkPackage.constructor({ enabled: colors });

// create an instance of spectral
const spectral = new Spectral();
spectral.registerFormat('oas2', isOpenApiv2);
spectral.registerFormat('oas3', isOpenApiv3);

// if the 'init' command is given, run the module
// and exit the program
if (args[0] === 'init') {
Expand Down Expand Up @@ -161,6 +171,17 @@ const processInput = async function(program) {
let originalFile;
let input;

// load the spectral ruleset, either a user's or the default ruleset
const spectralRuleset = await config.getSpectralRuleset(
defaultSpectralRuleset
);

try {
await spectral.loadRuleset(spectralRuleset);
} catch (err) {
return Promise.reject(err);
}

for (const validFile of filesToValidate) {
if (filesToValidate.length > 1) {
console.log(
Expand Down Expand Up @@ -232,10 +253,12 @@ const processInput = async function(program) {
process.chdir(originalWorkingDirectory);
}

// run validator, print the results, and determine if validator passed
// run validator & spectral, print the results, and determine if validator passed
let results;
try {
results = validator(swagger, configObject);
// let spectral handle the parsing of the original swagger/oa3 document
const spectralResults = await spectral.run(originalFile);
results = validator(swagger, configObject, spectralResults);
} catch (err) {
printError(chalk, 'There was a problem with a validator.', getError(err));
if (debug) {
Expand Down
22 changes: 22 additions & 0 deletions src/cli-validator/utils/processConfiguration.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,31 @@ const validateConfigOption = function(userOption, defaultOption) {
return result;
};

const getSpectralRuleset = async function(defaultRuleset) {
const ruleSetFileName = '.spectral.json';
let ruleSetFile;

// search up the file system for the first instance
// of '.spectral.json'
try {
ruleSetFile = await findUp(ruleSetFileName);
} catch (err) {
// if there's any issue finding a custom ruleset, then
// just use the default
ruleSetFile = defaultRuleset;
}

if (ruleSetFile === null) {
ruleSetFile = defaultRuleset;
}

return ruleSetFile;
};

module.exports.get = getConfigObject;
module.exports.validate = validateConfigObject;
module.exports.ignore = getFilesToIgnore;
module.exports.validateOption = validateConfigOption;
module.exports.validateLimits = validateLimits;
module.exports.limits = getLimits;
module.exports.getSpectralRuleset = getSpectralRuleset;
16 changes: 15 additions & 1 deletion src/cli-validator/utils/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const sharedSemanticValidators = require('require-all')(

const circularRefsValidator = require('./circular-references-ibm');

const spectralValidator = require('../../spectral/utils/spectral-validator');

const validators = {
'2': {
semanticValidators: semanticValidators2
Expand All @@ -29,7 +31,7 @@ const validators = {
};

// this function runs the validators on the swagger object
module.exports = function validateSwagger(allSpecs, config) {
module.exports = function validateSwagger(allSpecs, config, spectralResults) {
const version = getVersion(allSpecs.jsSpec);
allSpecs.isOAS3 = version === '3';
const { semanticValidators } = validators[version];
Expand All @@ -45,6 +47,18 @@ module.exports = function validateSwagger(allSpecs, config) {
const configSpecToUse = allSpecs.isOAS3 ? 'oas3' : 'swagger2';
config = merge(config.shared, config[configSpecToUse]);

// merge the spectral results
const parsedSpectralResults = spectralValidator.parseResults(spectralResults);
const key = 'spectral';
if (parsedSpectralResults.errors.length) {
validationResults.errors[key] = [...parsedSpectralResults.errors];
validationResults.error = true;
}
if (parsedSpectralResults.warnings.length) {
validationResults.warnings[key] = [...parsedSpectralResults.warnings];
validationResults.warning = true;
}

// run circular reference validator
if (allSpecs.circular) {
const problem = circularRefsValidator.validate(allSpecs, config);
Expand Down
27 changes: 27 additions & 0 deletions src/spectral/rulesets/.defaultsForSpectral.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"extends": [["spectral:oas", "off"]],
"formats": ["oas2", "oas3"],
"functionsDir": "../functions",
"rules": {
"no-eval-in-markdown": true,
"no-script-tags-in-markdown": true,
"openapi-tags": true,
"operation-description": true,
"operation-tags": true,
"operation-tag-defined": true,
"path-keys-no-trailing-slash": true,
"typed-enum": true,
"oas2-api-host": true,
"oas2-api-schemes": true,
"oas2-host-trailing-slash": true,
"oas2-valid-example": true,
"oas2-valid-definition-example": true,
"oas2-anyOf": true,
"oas2-oneOf": true,
"oas3-api-servers": true,
"oas3-examples-value-or-externalValue": true,
"oas3-server-trailing-slash": true,
"oas3-valid-example": true,
"oas3-valid-schema-example": true
}
}
44 changes: 44 additions & 0 deletions src/spectral/utils/spectral-validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const MessageCarrier = require('../../plugins/utils/messageCarrier');

const parseResults = function(results) {
const messages = new MessageCarrier();

if (results) {
for (const validationResult of results) {
if (validationResult) {
const code = validationResult['code'];
const severity = validationResult['severity'];
const message = validationResult['message'];
const path = validationResult['path'];

if (code && severity != null && message && path) {
if (code == 'parser') {
// Spectral doesn't allow disabling parser rules, so don't include them
// in the output for now
continue;
}
// Our validator only supports warning/error level, so only include
// those validation results (for now)
if (severity == '1') {
//warning
messages.addMessage(path, message, 'warning');
} else if (severity == '0') {
//error
messages.addMessage(path, message, 'error');
}
} else {
console.error(
'There was an error while parsing the spectral results: ',
JSON.stringify(validationResult)
);
continue;
}
}
}
}
return messages;
};

module.exports = {
parseResults
};
10 changes: 10 additions & 0 deletions test/cli-validator/mockFiles/oas3/clean.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
openapi: "3.0.0"
info:
description: Sample API definition that validates cleanly
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
contact:
email: "apiteam@swagger.io"
servers:
- url: http://petstore.swagger.io/v1
tags:
- name: pets
description: A pet
paths:
/pets:
get:
summary: List all pets
description: List all pets
operationId: list_pets
tags:
- pets
Expand Down Expand Up @@ -50,6 +58,7 @@ paths:
$ref: "#/components/schemas/Error"
post:
summary: Create a pet
description: Create a pet
operationId: create_pets
tags:
- pets
Expand All @@ -65,6 +74,7 @@ paths:
/pets/{pet_id}:
get:
summary: Info for a specific pet
description: Get information about a specific pet
operationId: get_pet_by_id
tags:
- pets
Expand Down
2 changes: 1 addition & 1 deletion test/cli-validator/tests/error-handling.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ describe('cli tool - test error handling', function() {
const capturedText = getCapturedText(consoleSpy.mock.calls);

expect(exitCode).toEqual(1);
expect(capturedText.length).toEqual(12);
expect(capturedText.length).toEqual(29);
expect(capturedText[0].trim()).toEqual(
'[Error] Trailing comma on line 36 of file ./test/cli-validator/mockFiles/trailing-comma.json.'
);
Expand Down

0 comments on commit ee2201f

Please sign in to comment.