Merge pull request #68 from amarzavery/sway4
changes made for better error reporting
This commit is contained in:
Коммит
dd00a71218
|
@ -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": {}
|
||||
|
|
15
README.md
15
README.md
|
@ -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
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'
|
||||
};
|
66
validate.js
66
validate.js
|
@ -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;
|
Загрузка…
Ссылка в новой задаче