Merge pull request #68 from amarzavery/sway4

changes made for better error reporting
This commit is contained in:
Amar Zavery 2017-02-24 11:10:05 -08:00 коммит произвёл GitHub
Родитель 18bbf4b1bb 693dda2ac8
Коммит dd00a71218
10 изменённых файлов: 213 добавлений и 95 удалений

4
.vscode/launch.json поставляемый
Просмотреть файл

@ -11,8 +11,8 @@
"program": "${workspaceRoot}/cli.js",
"cwd": "${workspaceRoot}",
"args": [
"validate-example",
"https://github.com/Azure/azure-rest-api-specs/blob/master/arm-redis/2016-04-01/swagger/redis.json",
"validate-spec",
"arm-storage/2016-01-01/swagger/storage.json",
"-j"
],
"env": {}

Просмотреть файл

@ -53,26 +53,23 @@ node cli.js
#### Command usage:
```
MacBook-Pro:openapi-validation-tools someUser$ >node cli.js -h
bash-3.2$ node cli.js
Commands:
live-test <spec-path> Performs live testing of x-ms-examples provided
for operations in the spec. This command will be
making live calls to the service that is
described in the given swagger spec.
validate-example <spec-path> Performs validation of x-ms-examples and
examples present in the spec.
validate-spec <spec-path> Performs semantic validation of the spec.
Options:
--version Show version number [boolean]
-j, --json Show json output [boolean]
-l, --logLevel Set the logging level for console.
[choices: "error", "warn", "info", "verbose", "debug", "silly"] [default:
"error"]
[choices: "off", "json", "error", "warn", "info", "verbose", "debug", "silly"]
[default: "warn"]
-f, --logFilepath Set the log file path. It must be an absolute filepath. By
default the logs will stored in a timestamp based log file
at "C:\Users\<username>\oav_output".
at "/Users/amarz/oav_output".
-h, --help Show help [boolean]
bash-3.2$
```
---

15
cli.js
Просмотреть файл

@ -16,28 +16,21 @@ yargs
.version("0.1.0")
.commandDir('lib/commands')
.option('h', {alias: 'help'})
.option('j', {alias: 'json', describe: 'Show json output', boolean: true})
.option('l', {
alias: 'logLevel',
describe: 'Set the logging level for console.',
choices: ['error', 'warn', 'info' , 'verbose', 'debug', 'silly'],
default: 'error'
choices: ['off', 'json', 'error', 'warn', 'info' , 'verbose', 'debug', 'silly'],
default: 'warn'
})
.option('f', {
alias: 'logFilepath',
describe: `Set the log file path. It must be an absolute filepath. ` +
`By default the logs will stored in a timestamp based log file at "${defaultLogDir}".`
})
.global(['h', 'j', 'l', 'f'])
.global(['h', 'l', 'f'])
.help()
.argv;
//setting console logging level to the value provided by the user.
log.consoleLogLevel = yargs.argv.l;
//setting the logFilePath if provided.
log.filepath = yargs.argv.f || logFilepath;
if (yargs.argv._.length === 0 && yargs.argv.h === false && yargs.argv.j === false) {
if (yargs.argv._.length === 0 && yargs.argv.h === false) {
yargs.coerce('help', function(arg) {return true;}).argv;
}

Просмотреть файл

@ -7,30 +7,30 @@ var util = require('util'),
log = require('../util/logging'),
validate = require('../../validate');
exports.command = 'live-test <spec-path>';
// exports.command = 'live-test <spec-path>';
exports.describe = 'Performs live testing of x-ms-examples provided for operations in the spec. ' +
'This command will be making live calls to the service that is described in the given swagger spec.';
// exports.describe = 'Performs live testing of x-ms-examples provided for operations in the spec. ' +
// 'This command will be making live calls to the service that is described in the given swagger spec.';
exports.builder = {
o: {
alias: 'operationIds',
describe: 'A comma separated string of operationIds for which the examples ' +
'need to be validated. If operationIds are not provided then the entire spec will be validated. ' +
'Example: "StorageAccounts_Create, StorageAccounts_List, Usages_List".',
string: true
}
};
// exports.builder = {
// o: {
// alias: 'operationIds',
// describe: 'A comma separated string of operationIds for which the examples ' +
// 'need to be validated. If operationIds are not provided then the entire spec will be validated. ' +
// 'Example: "StorageAccounts_Create, StorageAccounts_List, Usages_List".',
// string: true
// }
// };
exports.handler = function (argv) {
log.debug(argv);
let specPath = argv.specPath;
let operationIds = argv.operationIds;
if (specPath.match(/.*composite.*/ig) !== null) {
return validate.validateExamplesInCompositeSpec(specPath);
} else {
return new ExecutionEngine().liveTest(specPath, operationIds);
}
}
// exports.handler = function (argv) {
// log.debug(argv);
// let specPath = argv.specPath;
// let operationIds = argv.operationIds;
// if (specPath.match(/.*composite.*/ig) !== null) {
// return validate.validateExamplesInCompositeSpec(specPath);
// } else {
// return new ExecutionEngine().liveTest(specPath, operationIds);
// }
// }
exports = module.exports;
// exports = module.exports;

Просмотреть файл

@ -24,11 +24,12 @@ exports.handler = function (argv) {
log.debug(argv);
let specPath = argv.specPath;
let operationIds = argv.operationIds;
let json = argv.json;
let consoleLogLevel = argv.logLevel;
let logfilepath = argv.f;
if (specPath.match(/.*composite.*/ig) !== null) {
return validate.validateExamplesInCompositeSpec(specPath);
return validate.validateExamplesInCompositeSpec(specPath, consoleLogLevel, logfilepath);
} else {
return validate.validateExamples(specPath, operationIds, json);
return validate.validateExamples(specPath, operationIds, consoleLogLevel, logfilepath);
}
}

Просмотреть файл

@ -13,11 +13,13 @@ exports.describe = 'Performs semantic validation of the spec.';
exports.handler = function (argv) {
log.debug(argv);
let specPath = argv.specPath;
let json = argv.json;
let consoleLogLevel = argv.logLevel;
let logfilepath = argv.f;
if (specPath.match(/.*composite.*/ig) !== null) {
return validate.validateCompositeSpec(specPath, json);
return validate.validateCompositeSpec(specPath, consoleLogLevel, logfilepath);
} else {
return validate.validateSpec(specPath, json);
return validate.validateSpec(specPath, consoleLogLevel, logfilepath);
}
};

Просмотреть файл

@ -13,6 +13,7 @@ var util = require('util'),
Constants = require('./util/constants'),
log = require('./util/logging'),
ResponseWrapper = require('./responseWrapper'),
ValidationResponse = require('./util/validationResponse'),
ErrorCodes = Constants.ErrorCodes;
/*
@ -75,6 +76,26 @@ class SpecValidator {
});
}
getProviderNamespace() {
let result = null;
let re = /^(.*)\/providers\/(\w+\.\w+)\/(.*)$/ig;
if (this.specInJson) {
if (this.specInJson.paths) {
let paths = Object.keys(this.specInJson.paths);
if (paths) {
for (let i = 0; i < paths.length; i++) {
let res = re.exec(paths[i]);
if (res && res[2]) {
result = res[2];
break;
}
}
}
}
}
return result;
}
/*
* Updates the validityStatus of the internal specValidationResult based on the provided value.
*
@ -119,6 +140,8 @@ class SpecValidator {
let self = this;
self.specValidationResult.validateSpec = {};
self.specValidationResult.validateSpec.isValid = true;
self.specValidationResult.validateSpec.errors = [];
self.specValidationResult.validateSpec.warnings = []
if (!self.swaggerApi) {
let msg = `Please call "specValidator.initialize()" before calling this method, so that swaggerApi is populated.`;
let e = self.constructErrorObject(ErrorCodes.InitializationError, msg)
@ -133,7 +156,7 @@ class SpecValidator {
if (validationResult.errors && validationResult.errors.length) {
self.specValidationResult.validateSpec.isValid = false;
let e = self.constructErrorObject(ErrorCodes.SemanticValidationError, `The spec ${self.specPath} has semantic validation errors.`, validationResult.errors);
self.specValidationResult.validateSpec.error = e;
self.specValidationResult.validateSpec.errors = ValidationResponse.constructErrors(e, self.specPath, self.getProviderNamespace());
log.error(Constants.Errors);
log.error('------');
self.updateValidityStatus();
@ -142,10 +165,13 @@ class SpecValidator {
self.specValidationResult.validateSpec.result = `The spec ${self.specPath} is sematically valid.`
}
if (validationResult.warnings && validationResult.warnings.length > 0) {
self.specValidationResult.validateSpec.warning = validationResult.warnings;
log.warn(Constants.Warnings);
log.warn('--------');
log.warn(util.inspect(validationResult.warnings));
let warnings = ValidationResponse.sanitizeWarnings(validationResult.warnings);
if (warnings && warnings.length) {
self.specValidationResult.validateSpec.warnings = warnings;
log.warn(Constants.Warnings);
log.warn('--------');
log.warn(util.inspect(warnings));
}
}
}
return Promise.resolve(validationResult);

Просмотреть файл

@ -34,6 +34,16 @@ function getTimeStamp() {
+ pad(now.getMinutes())
+ pad(now.getSeconds());
}
var customLogLevels = {
off: 0,
json: 1,
error: 2,
warn: 3,
info: 4,
verbose: 5,
debug: 6,
silly: 7
};
var logger = new (winston.Logger)({
transports: [
@ -43,7 +53,8 @@ var logger = new (winston.Logger)({
prettyPrint: true,
humanReadableUnhandledException: true
})
]
],
levels: customLogLevels
});
Object.defineProperties(logger, {
@ -54,7 +65,7 @@ Object.defineProperties(logger, {
if (!level) {
level = 'warn';
}
let validLevels = ['error', 'warn', 'info', 'verbose', 'debug', 'silly'];
let validLevels = Object.keys(customLogLevels);
if (!validLevels.some(function (item) { return item === level; })) {
throw new Error(`The logging level provided is "${level}". Valid values are: "${validLevels}".`);
}

Просмотреть файл

@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
'use strict';
var pointer = require('json-pointer');
exports = module.exports;
function foo(type, code, message, innerErrors, jsonref, jsonpath, id, validationCategory, providerNamespace, resourceType) {
this.code = code;
this.code = code;
this.message = message;
this.innerErrors = innerErrors;
this.jsonref = jsonref;
this.jsonpath = jsonpath;
this.id = id;
this.validationCategory = validationCategory;
this.providerNamespace = providerNamespace;
this.resourceType = resourceType;
}
exports.serialize = function seralize() {
let result = {};
for (let prop in this) {
if (this[prop] !== null && this[prop] !== undefined) {
if (prop === 'jsonpath')
result['json-path'] = this[prop];
}
}
return result;
}
exports.constructErrors = function constructErrors(validationError, specPath, providerNamespace) {
if (!validationError) {
throw new Error('validationError cannot be null or undefined.');
}
let result = [];
validationError.innerErrors.forEach(function (error) {
let e = {
validationCategory: 'SwaggerViolation',
providerNamespace: providerNamespace,
type: 'error',
inner: error.inner
};
if (error.code && exports.mapper[error.code]) {
e.code = error.code;
e.id = exports.mapper[error.code];
e.message = error.message;
} else {
e.code = 'SWAGGER_SCHEMA_VALIDATION_ERROR';
e.message = validationError.message;
e.id = exports.mapper[e.code];
e.inner = error;
}
if (error.path && error.path.length) {
let paths = [specPath + '#'].concat(error.path);
let jsonpath = pointer.compile(paths);
e.jsonref = jsonpath;
e['json-path'] = pointer.unescape(jsonpath);
}
result.push(e);
});
return result;
};
exports.sanitizeWarnings = function sanitizeWarnings(warnings) {
if (!warnings) {
throw new Error('validationError cannot be null or undefined.');
}
let result = [];
warnings.forEach(function(warning) {
if (warning.code && warning.code !== 'EXTRA_REFERENCE_PROPERTIES' && warning.code !== 'UNUSED_DEFINITION') {
result.push(warning);
}
});
return result;
}
exports.mapper = {
'SWAGGER_SCHEMA_VALIDATION_ERROR': 'M6000',
'INVALID_PARAMETER_COMBINATION': 'M6001',
'MULTIPLE_BODY_PARAMETERS': 'M6002',
'DUPLICATE_PARAMETER': 'M6003',
'DUPLICATE_OPERATIONID': 'M6004',
'MISSING_PATH_PARAMETER_DEFINITION': 'M6005',
'EMPTY_PATH_PARAMETER_DECLARATION': 'M6006',
'MISSING_PATH_PARAMETER_DEFINITION': 'M6007',
'EQUIVALENT_PATH': 'M6008',
'DUPLICATE_PARAMETER': 'M6009',
'UNRESOLVABLE_REFERENCE': 'M6010',
'INVALID_TYPE': 'M6011',
'CIRCULAR_INHERITANCE': 'M6012',
'OBJECT_MISSING_REQUIRED_PROPERTY': 'M6013',
'OBJECT_MISSING_REQUIRED_PROPERTY_DEFINITION': 'M6014',
'ENUM_MISMATCH': 'M6015'
};

Просмотреть файл

@ -44,21 +44,17 @@ exports.getDocumentsFromCompositeSwagger = function getDocumentsFromCompositeSwa
});
};
exports.validateSpec = function validateSpec(specPath, json, consoleLogLevel, logFilepath) {
if (consoleLogLevel) { log.consoleLogLevel = consoleLogLevel; }
if (logFilepath) {
log.filepath = logFilepath;
} else {
log.filepath = log.filepath;
}
exports.validateSpec = function validateSpec(specPath, consoleLogLevel, logFilepath) {
log.consoleLogLevel = consoleLogLevel || log.consoleLevel;
log.filepath = logFilepath || log.filepath;
let validator = new SpecValidator(specPath);
exports.finalValidationResult[specPath] = validator.specValidationResult;
return validator.initialize().then(function () {
log.info(`Semantically validating ${specPath}:\n`);
return validator.validateSpec().then(function (result) {
exports.updateEndResultOfSingleValidation(validator);
exports.logDetailedInfo(validator, json);
return Promise.resolve(result);
exports.logDetailedInfo(validator);
return Promise.resolve(validator.specValidationResult);
});
}).catch(function (err) {
log.error(err);
@ -66,10 +62,12 @@ exports.validateSpec = function validateSpec(specPath, json, consoleLogLevel, lo
});
};
exports.validateCompositeSpec = function validateCompositeSpec(compositeSpecPath, json) {
exports.validateCompositeSpec = function validateCompositeSpec(compositeSpecPath, consoleLogLevel, logFilepath) {
log.consoleLogLevel = consoleLogLevel || log.consoleLevel;
log.filepath = logFilepath || log.filepath;
return exports.getDocumentsFromCompositeSwagger(compositeSpecPath).then(function (docs) {
let promiseFactories = docs.map(function (doc) {
return exports.validateSpec(doc, json);
return function () { return exports.validateSpec(doc, consoleLogLevel, logFilepath) };
});
return utils.executePromisesSequentially(promiseFactories);
}).catch(function (err) {
@ -78,20 +76,16 @@ exports.validateCompositeSpec = function validateCompositeSpec(compositeSpecPath
});
};
exports.validateExamples = function validateExamples(specPath, operationIds, json, consoleLogLevel, logFilepath) {
if (consoleLogLevel) { log.consoleLogLevel = consoleLogLevel; }
if (logFilepath) {
log.filepath = logFilepath;
} else {
log.filepath = log.filepath;
}
exports.validateExamples = function validateExamples(specPath, operationIds, consoleLogLevel, logFilepath) {
log.consoleLogLevel = consoleLogLevel || log.consoleLevel;
log.filepath = logFilepath || log.filepath;
let validator = new SpecValidator(specPath);
exports.finalValidationResult[specPath] = validator.specValidationResult;
return validator.initialize().then(function () {
log.info(`Validating "examples" and "x-ms-examples" in ${specPath}:\n`);
validator.validateOperations(operationIds);
exports.updateEndResultOfSingleValidation(validator);
exports.logDetailedInfo(validator, json);
exports.logDetailedInfo(validator);
return Promise.resolve(validator.specValidationResult);
}).catch(function (err) {
log.error(err);
@ -99,10 +93,12 @@ exports.validateExamples = function validateExamples(specPath, operationIds, jso
});
};
exports.validateExamplesInCompositeSpec = function validateExamplesInCompositeSpec(compositeSpecPath, json) {
exports.validateExamplesInCompositeSpec = function validateExamplesInCompositeSpec(compositeSpecPath, consoleLogLevel, logFilepath) {
log.consoleLogLevel = consoleLogLevel || log.consoleLevel;
log.filepath = logFilepath || log.filepath;
return exports.getDocumentsFromCompositeSwagger(compositeSpecPath).then(function (docs) {
let promiseFactories = docs.map(function (doc) {
return exports.validateExamples(doc, json);
return function () { return exports.validateExamples(doc, consoleLogLevel, logFilepath); }
});
return utils.executePromisesSequentially(promiseFactories);
}).catch(function (err) {
@ -113,10 +109,12 @@ exports.validateExamplesInCompositeSpec = function validateExamplesInCompositeSp
exports.updateEndResultOfSingleValidation = function updateEndResultOfSingleValidation(validator) {
if (validator.specValidationResult.validityStatus) {
let consoleLevel = log.consoleLogLevel;
log.consoleLogLevel = 'info';
log.info('No Errors were found.');
log.consoleLogLevel = consoleLevel;
if (!(log.consoleLogLevel === 'json' || log.consoleLogLevel === 'off')) {
let consoleLevel = log.consoleLogLevel;
log.consoleLogLevel = 'info';
log.info('No Errors were found.');
log.consoleLogLevel = consoleLevel;
}
}
if (!validator.specValidationResult.validityStatus) {
exports.finalValidationResult.validityStatus = validator.specValidationResult.validityStatus;
@ -124,19 +122,13 @@ exports.updateEndResultOfSingleValidation = function updateEndResultOfSingleVali
return;
};
exports.logDetailedInfo = function logDetailedInfo(validator, json) {
if (json) {
let consoleLevel = log.consoleLogLevel;
log.consoleLogLevel = 'info';
log.info('############################');
log.info(validator.specValidationResult);
log.info('----------------------------');
log.consoleLogLevel = consoleLevel;
} else {
log.silly('############################');
log.silly(validator.specValidationResult);
log.silly('----------------------------');
exports.logDetailedInfo = function logDetailedInfo(validator) {
if (log.consoleLogLevel === 'json') {
console.dir(validator.specValidationResult, { depth: null, colors: true });
}
log.silly('############################');
log.silly(validator.specValidationResult);
log.silly('----------------------------');
};
exports = module.exports;