Merge pull request #4 from amarzavery/sway2

Sway2
This commit is contained in:
Amar Zavery 2017-01-26 21:05:38 -08:00 коммит произвёл GitHub
Родитель 127930a667 8e2eea21c1
Коммит 4abe7299a9
12 изменённых файлов: 1667 добавлений и 281 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -21,6 +21,7 @@ bin/*
/.vscode/*
ValidationTool.njsproj
ValidationTool.sln
.vscode/launch.json
#### win gitignore

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

@ -12,9 +12,7 @@
"cwd": "${workspaceRoot}",
"args": [
"validate-example",
"..\\upstream\\azure-rest-api-specs\\arm-customer-insights\\2016-01-01\\swagger\\customer-insights.json",
"-o",
"Interactions_CreateOrUpdate",
"https://github.com/Azure/azure-rest-api-specs/blob/master/arm-redis/2016-04-01/swagger/redis.json",
"-j"
],
"env": {

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

@ -3,6 +3,7 @@
'use strict';
var util = require('util'),
ExecutionEngine = require('../executionEngine'),
log = require('../util/logging'),
validate = require('../../validate');
@ -27,7 +28,7 @@ exports.handler = function (argv) {
if (specPath.match(/.*composite.*/ig) !== null) {
return validate.validateExamplesInCompositeSpec(specPath);
} else {
return validate.liveTest(specPath, operationIds);
return new ExecutionEngine().liveTest(specPath, operationIds);
}
}

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

@ -3,7 +3,125 @@
'use strict';
var util = require('util'),
msRest = require('ms-rest'),
msrestazure = require('ms-rest-azure'),
ResourceManagementClient = require('azure-arm-resource').ResourceManagementClient,
HttpRequest = msRest.WebResource,
ResponseWrapper = require('./responseWrapper'),
SpecValidator = require('./specValidator'),
utils = require('./util/utils'),
log = require('./util/logging'),
Constants = require('./util/constants'),
EnvironmentVariables = Constants.EnvironmentVariables;
class ExecutionEngine {
constructor() {
}
sanitizeParameters(exampleParameters) {
let self = this;
if (exampleParameters) {
if (exampleParameters.subscriptionId) {
exampleParameters.subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
}
if (exampleParameters.resourceGroupName) {
exampleParameters.resourceGroupName = process.env['AZURE_RESOURCE_GROUP'];
}
}
return exampleParameters;
};
liveTest(specPath, operationId) {
let self = this;
self.validateEnvironmentVariables();
log.transports.console.level = 'info';
let clientId = process.env[EnvironmentVariables.ClientId];
let domain = process.env[EnvironmentVariables.Domain];
let secret = process.env[EnvironmentVariables.ApplicationSecret];
let subscriptionId = process.env[EnvironmentVariables.AzureSubscriptionId];
let location = process.env[EnvironmentVariables.AzureLocation] || 'westus';
let resourceGroupName = process.env[EnvironmentVariables.AzureResourcegroup] || utils.generateRandomId('testrg');
let validator = new SpecValidator(specPath);
let operation, xmsExamples;
validator.initialize().then(function () {
log.info(`Running live test using x-ms-examples in ${operationId} in ${specPath}:\n`);
operation = validator.getOperationById(operationId);
xmsExamples = operation[Constants.xmsExamples];
if (xmsExamples) {
for (let scenario in xmsExamples) {
let xmsExample = xmsExamples[scenario];
let parameters = self.sanitizeParameters(xmsExample.parameters);
let result = validator.validateRequest(operation, xmsExample.parameters);
if (result.validationResult && result.validationResult.errors && result.validationResult.errors.length) {
let msg = `Cannot proceed ahead with the live test for operation: ${operationId}. Found validation errors in the request.\n` +
`${util.inspect(result.validationResult.errors, { depth: null })}`;
throw new Error(msg);
}
let req = result.request;
if (!req) {
throw new Error(`Cannot proceed ahead with the live test for operation: ${operationId}. The request object is undefined.`);
}
if (req.body !== null && req.body !== undefined) {
req.body = JSON.stringify(req.body);
}
msrestazure.loginWithServicePrincipalSecret(clientId, secret, domain, function (err, creds, subscriptions) {
if (err) {
throw err;
}
let resourceClient = new ResourceManagementClient(creds, subscriptionId);
let client = new msrestazure.AzureServiceClient(creds);
self.createResourceGroup(resourceClient, location, resourceGroupName, function (err, resourceGroup) {
if (err) {
throw err;
}
client.sendRequest(req, function (err, result, request, response) {
log.info(request);
log.info(response);
if (err) {
throw err;
}
log.info(result);
});
});
});
}
}
}).catch(function (err) {
log.error(err);
});
}
createResourceGroup(resourceClient, location, resourceGroupName, callback) {
let self = this;
resourceClient.resourceGroups.get(resourceGroupName, function (err, result, request, response) {
if (err && err.statusCode === 404) {
log.info(`Creating resource group: \'${resourceGroupName}\', if not present.`);
let groupParameters = { location: location, tags: { 'live-test': 'live-test' } };
return resourceClient.resourceGroups.createOrUpdate(resourceGroupName, groupParameters, callback);
} else {
return callback(null, result);
}
});
}
getResourceGroup(resourceClient, resourceGroupName, callback) {
let self = this;
log.info(`Searching ResourceGroup: ${resourceGroupName}.`)
resourceClient.resourceGroups.get(resourceGroupName, callback);
}
validateEnvironmentVariables() {
let self = this;
var envs = [];
if (!process.env[EnvironmentVariables.ClientId]) envs.push(EnvironmentVariables.ClientId);
if (!process.env[EnvironmentVariables.Domain]) envs.push(EnvironmentVariables.Domain);
if (!process.env[EnvironmentVariables.ApplicationSecret]) envs.push(EnvironmentVariables.ApplicationSecret);
if (!process.env[EnvironmentVariables.AzureSubscriptionId]) envs.push(EnvironmentVariables.AzureSubscriptionId);
if (envs.length > 0) {
throw new Error(util.format('please set/export the following environment variables: %s', envs.toString()));
}
}
}

233
lib/specResolver.js Normal file
Просмотреть файл

@ -0,0 +1,233 @@
// 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 util = require('util'),
path = require('path'),
JsonRefs = require('json-refs'),
utils = require('./util/utils'),
Constants = require('./util/constants'),
log = require('./util/logging'),
ErrorCodes = Constants.ErrorCodes;
class SpecResolver {
constructor(specPath, specInJson) {
if (specPath === null || specPath === undefined || typeof specPath.valueOf() !== 'string' || !specPath.trim().length) {
throw new Error('specPath is a required property of type string and it cannot be an empty string.')
}
if (specInJson === null || specInJson === undefined || typeof specInJson !== 'object') {
throw new Error('specInJson is a required property of type object')
}
this.specInJson = specInJson;
this.specPath = specPath;
this.specDir = path.dirname(this.specPath);
this.visitedEntities = {};
this.resolvedAllOfModels = {};
}
unifyXmsPaths() {
let self = this;
//unify x-ms-paths into paths
let xmsPaths = self.specInJson['x-ms-paths'];
let paths = self.specInJson.paths;
if (xmsPaths && xmsPaths instanceof Object && Object.keys(xmsPaths).length > 0) {
for (let property in xmsPaths) {
paths[property] = xmsPaths[property];
}
self.specInJson.paths = utils.mergeObjects(xmsPaths, paths);
}
return Promise.resolve(self);
}
resolve() {
let self = this;
return self.unifyXmsPaths()
.then(function() { return self.resolveRelativePaths(); })
.then(function() { self.resolveAllOfInDefinitions(); })
.then(function() { self.deleteReferencesToAllOf(); })
.then(function() { self.setAdditionalPropertiesFalse(); });
}
resolveRelativePaths(doc, docPath, filterType) {
let self = this;
let docDir;
let options = {
relativeBase: docDir,
filter: ['relative', 'remote']
};
if (!doc) {
doc = self.specInJson;
}
if (!docPath) {
docPath = self.specPath;
docDir = self.specDir;
}
if (!docDir) {
docDir = path.dirname(docPath);
}
if (filterType === 'all') {
delete options.filter;
}
let allRefsRemoteRelative = JsonRefs.findRefs(doc, options);
let promiseFactories = Object.keys(allRefsRemoteRelative).map(function (refName) {
let refDetails = allRefsRemoteRelative[refName];
return function () { return self.resolveRelativeReference(refName, refDetails, docPath); };
});
if (promiseFactories.length) {
return utils.executePromisesSequentially(promiseFactories);
} else {
return Promise.resolve();
}
}
resolveRelativeReference(refName, refDetails, docPath) {
let self = this;
if (!docPath) {
docPath = self.specPath;
}
let node = refDetails.def;
let slicedRefName = refName.slice(1);
let reference = node['$ref'];
let parsedReference = utils.parseReferenceInSwagger(reference);
if (parsedReference.filePath) {
//assuming that everything in the spec is relative to it, let us join the spec directory and the file path in reference.
docPath = utils.joinPath(self.specDir, parsedReference.filePath);
}
return utils.parseJson(docPath).then(function (result) {
if (!parsedReference.localReference) {
//Since there is no local reference we will replace the key in the object with the parsed json (relative) file it is refering to.
return Promise.resolve(utils.setObject(self.specInJson, slicedRefName, result));
} else {
//resolve the local reference.
//make the reference local to the doc being processed
node['$ref'] = parsedReference.localReference.value;
utils.setObject(self.specInJson, slicedRefName, node);
let slicedLocalReferenceValue = parsedReference.localReference.value.slice(1);
let referencedObj = self.visitedEntities[slicedLocalReferenceValue];
if (!referencedObj) {
//We get the definition/parameter from the relative file and then add it (make it local) to the doc being processed.
referencedObj = utils.getObject(result, slicedLocalReferenceValue);
utils.setObject(self.specInJson, slicedLocalReferenceValue, referencedObj);
self.visitedEntities[slicedLocalReferenceValue] = referencedObj;
return Promise.resolve(self.resolveRelativePaths(referencedObj, docPath, 'all'));
} else {
return Promise.resolve();
}
}
});
}
resolveAllOfInDefinitions() {
let self = this;
let spec = self.specInJson;
let definitions = spec.definitions;
let modelNames = Object.keys(self.specInJson.definitions);
modelNames.map(function (modelName) {
let model = definitions[modelName];
let modelRef = '/definitions/' + modelName;
return self.resolveAllOfInModel(model, modelRef);
});
return Promise.resolve(self);
}
resolveAllOfInModel(model, modelRef) {
let self = this;
let spec = self.specInJson;
if (!model || (model && typeof model !== 'object')) {
throw new Error(`model cannot be null or undefined and must of type "object".`);
}
if (!modelRef || (modelRef && typeof modelRef.valueOf() !== 'string')) {
throw new Error(`model cannot be null or undefined and must of type "string".`);
}
if (modelRef.startsWith('#')) modelRef = modelRef.slice(1);
if (!self.resolvedAllOfModels[modelRef]) {
if (model && model.allOf) {
model.allOf.map(function (item) {
let referencedModel = item;
let ref = item['$ref'];
let slicedRef = ref ? ref.slice(1) : undefined;
if (ref) {
referencedModel = utils.getObject(spec, slicedRef);
}
if (referencedModel.allOf) {
self.resolveAllOfInModel(referencedModel, slicedRef);
}
model = self.mergeParentAllOfInChild(referencedModel, model);
self.resolvedAllOfModels[slicedRef] = referencedModel;
return model;
});
} else {
self.resolvedAllOfModels[modelRef] = model;
return model;
}
}
}
mergeParentAllOfInChild(source, target) {
let self = this;
if (!source || (source && typeof source !== 'object')) {
throw new Error(`source must be of type "object".`);
}
if (!target || (target && typeof target !== 'object')) {
throw new Error(`target must be of type "object".`);
}
//merge the source (Resource) model's properties into the properties
//of the target (StorageAccount) model.
target.properties = utils.mergeObjects(source.properties, target.properties);
//merge the array of required properties
if (source.required) {
if (!target.required) {
target.required = [];
}
target.required = [...new Set([...source.required, ...target.required])];
}
//merge x-ms-azure-resource
if (source['x-ms-azure-resource']) {
target['x-ms-azure-resource'] = source['x-ms-azure-resource'];
}
return target;
}
deleteReferencesToAllOf() {
let self = this;
let spec = self.specInJson;
let definitions = spec.definitions;
let modelNames = Object.keys(definitions);
modelNames.map(function (modelName) {
if (definitions[modelName].allOf) {
delete definitions[modelName].allOf;
}
});
return Promise.resolve(self);
}
setAdditionalPropertiesFalse(modelNames, force) {
let self = this;
let spec = self.specInJson;
let definitions = spec.definitions;
if (!modelNames) {
modelNames = Object.keys(definitions);
}
modelNames.forEach(function iterator(modelName) {
let model = definitions[modelName];
if (model) {
if (!model.additionalProperties || force) {
model.additionalProperties = false;
}
}
});
return Promise.resolve(self);
}
}
module.exports = SpecResolver;

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

@ -5,11 +5,10 @@
var util = require('util'),
path = require('path'),
fs = require('fs'),
Sway = require('sway'),
msRest = require('ms-rest'),
JsonRefs = require('json-refs'),
HttpRequest = msRest.WebResource,
SpecResolver = require('./specResolver'),
utils = require('./util/utils'),
Constants = require('./util/constants'),
log = require('./util/logging'),
@ -29,146 +28,36 @@ class SpecValidator {
this.specPath = specPath;
this.specDir = path.dirname(this.specPath);
this.specInJson = specInJson;
this.specResolver = null;
this.specValidationResult = { validityStatus: true, operations: { } };
this.swaggerApi = null;
}
unifyXmsPaths() {
//unify x-ms-paths into paths
if (this.specInJson['x-ms-paths'] && this.specInJson['x-ms-paths'] instanceof Object &&
Object.keys(this.specInJson['x-ms-paths']).length > 0) {
let paths = this.specInJson.paths;
for (let property in this.specInJson['x-ms-paths']) {
paths[property] = this.specInJson['x-ms-paths'][property];
}
this.specInJson.paths = paths;
}
}
findObject(what, where, actualReference, docPath) {
initialize() {
let self = this;
let result = eval(`where.${what}`);
if (!result) {
let msg = `Object '${what}' from the given reference ` +
`'${actualReference}' is not found in the swagger spec '${docPath}'.`;
let e = self.constructErrorObject('OBJECT_NOT_FOUND', msg);
log.error(e);
throw e;
} else {
return result;
}
}
getDefinitionFromReference(reference) {
let self = this;
if (!reference || (reference && reference.trim().length === 0)) {
throw new Error('reference cannot be null or undefined and it must be a non-empty string.');
}
let refObj = utils.parseReferenceInSwagger(reference);
if (refObj.filePath) {
let docPath = utils.joinPath(self.specDir, refObj.filePath);
return utils.parseJson(docPath).then(function(result) {
return self.findObject(refObj.localReference.accessorProperty, result, refObj.localReference.value, docPath);
});
} else {
return self.findObject(refObj.localReference.accessorProperty, self.specInJson, refObj.localReference.value, self.specPath);
}
}
mergeResolvedAllOfObjects(source, target) {
let self = this;
if (!source || (source && typeof source !== 'object')) {
return Promise.reject(new Error(`source must be of type "object".`));
}
if (!target || (target && typeof target !== 'object')) {
return Promise.reject(new Error(`target must be of type "object".`));
}
//merge the target model's properties
source.properties = utils.merge(source.properties, target.properties);
//merge the array of required properties
if (target.required) {
if (!source.required) {
source.required = [];
}
source.required = [...new Set([...source.required, ...target.required])];
}
//merge x-ms-azure-resource
if (target['x-ms-azure-resource']) {
source['x-ms-azure-resource'] = target['x-ms-azure-resource'];
}
return Promise.resolve(source);
}
getRefDefinitionInAllOf(item) {
let self = this;
if (!item || (item && typeof item !== 'object')) {
return Promise.reject(new Error(`item must be of type "object".`));
}
if (item['$ref']) {
return Promise.resolve(self.getDefinitionFromReference(item['$ref']));
} else {
return Promise.resolve(item);
}
}
composeAllOf(model, item) {
let self = this;
return self.getRefDefinitionInAllOf(item).then(function(result) {
if (result && result.allOf) {
return self.resolveAllOf(result).then(function(res) {
return Promise.resolve(self.mergeResolvedAllOfObjects(model, result));
}).catch(function (err) {
log.error(err);
return Promise.reject(err);
});
} else {
return Promise.resolve(self.mergeResolvedAllOfObjects(model, result));
}
return utils.parseJson(self.specPath).then(function (result) {
self.specInJson = result;
self.specResolver = new SpecResolver(self.specPath, self.specInJson);
return self.specResolver.resolve();
}).then(function() {
let options = {};
options.definition = self.specInJson;
options.jsonRefs = {};
options.jsonRefs.relativeBase = self.specDir;
return Sway.create(options);
}).then(function (api) {
self.swaggerApi = api;
return Promise.resolve(api);
}).catch(function (err) {
console.dir(err);
let e = self.constructErrorObject(ErrorCodes.ResolveSpecError, err.message, [err]);
self.specValidationResult.resolveSpec = e;
log.error(`${ErrorCodes.ResolveSpecError}: ${err.message}.`);
log.silly(err.stack);
return Promise.reject(e);
});
}
resolveAllOf(model) {
let self = this;
if (!model || (model && typeof model !== 'object')) {
return Promise.reject(new Error(`model cannot be null or undefined and must of type object.`));
}
if (model.allOf) {
let allOfItemPromiseFactories = model.allOf.map(function(item) {
return Promise.resolve(self.composeAllOf(model, item));
});
return utils.executePromisesSequentially(allOfItemPromiseFactories);
}
return Promise.resolve(model);
}
makeModelsStricter() {
let self = this;
let spec = self.specInJson;
let definitions = spec.definitions;
let modelNames = Object.keys(self.specInJson.definitions);
let modelPromiseFactories = modelNames.map(function (modelName) {
let model = definitions[modelName];
if (model && !model.additionalProperties) {
model.additionalProperties = false;
}
return self.resolveAllOf(model);
});
return utils.executePromisesSequentially(modelPromiseFactories);
}
// resolveRelativePaths() {
// let self = this;
// let spec = self.specInJson;
// let options = {
// relativeBase: self.specDir,
// filter: ['relative', 'remote']
// };
// let allRefsRemoteRelative = JsonRefs.findRefs(spec, options);
// Object.keys(allRefsRemoteRelative).map(function(refName) {
// let refDetails = allRefsRemoteRelative[refName];
// })
// }
updateValidityStatus(value) {
if (!Boolean(value)) {
this.specValidationResult.validityStatus = false;
@ -192,40 +81,6 @@ class SpecValidator {
return err;
}
initialize() {
let self = this;
return utils.parseJson(self.specPath).then(function (result) {
self.specInJson = result;
self.unifyXmsPaths();
return self;
}).then(function () {
return self.makeModelsStricter();
}).then(function() {
return Object.keys(self.specInJson.definitions).map(function(definitionName){
if (self.specInJson.definitions[definitionName].allOf) {
delete self.specInJson.definitions[definitionName].allOf;
}
});
}).then(function() {
let options = {};
options.definition = self.specInJson;
options.jsonRefs = {};
options.jsonRefs.includeInvalid = true;
options.jsonRefs.relativeBase = self.specDir;
return Sway.create(options);
}).then(function (api) {
self.swaggerApi = api;
return Promise.resolve(api);
}).catch(function (err) {
console.dir(err);
let e = self.constructErrorObject(ErrorCodes.ResolveSpecError, err.message, [err]);
self.specValidationResult.resolveSpec = e;
log.error(`${ErrorCodes.ResolveSpecError}: ${err.message}.`);
log.silly(err.stack);
return Promise.reject(e);
});
}
validateSpec() {
let self = this;
self.specValidationResult.validateSpec = {};

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

@ -0,0 +1,634 @@
// 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 fs = require('fs'),
path = require('path'),
util = require('util'),
swt = require('swagger-tools').specs.v2,
RefParser = require('json-schema-ref-parser'),
utils = require('./util/utils'),
cwd = process.cwd();
const constraints = ['minLength', 'maxLength', 'minimum', 'maximum', 'enum',
'maxItems', 'minItems', 'uniqueItems', 'multipleOf', 'pattern'];
const xmsExamples = 'x-ms-examples', exampleInSpec = 'example-in-spec', BodyParameterValid = 'BODY_PARAMAETER_VALID',
InternalError = 'INTERNAL_ERROR', RefNotFoundError = 'REF_NOTFOUND_ERROR',
JsonParsingError = 'JSON_PARSING_ERROR', XmsExampleNotFoundError = 'X-MS-EXAMPLE_NOTFOUND_ERROR',
ResponseBodyValidationError = 'RESPONSE_BODY_VALIDATION_ERROR',
RequiredParameterNotInExampleError = 'REQUIRED_PARAMETER_NOT_IN_EXAMPLE_ERROR',
BodyParameterValidationError = 'BODY_PARAMETER_VALIDATION_ERROR', TypeValidationError = 'TYPE_VALIDATION_ERROR',
ConstraintValidationError = 'CONSTRAINT_VALIDATION_ERROR', StatuscodeNotInExampleError = 'STATUS_CODE_NOT_IN_EXAMPLE_ERROR';
class SpecValidator2 {
constructor(filePath) {
this.filePath = filePath;
this.specInJson = null;
this.refSpecInJson = null;
this.specValidationResult = {};
}
unifyXmsPaths() {
//unify x-ms-paths into paths
if (this.specInJson['x-ms-paths'] && this.specInJson['x-ms-paths'] instanceof Object &&
Object.keys(this.specInJson['x-ms-paths']).length > 0) {
let paths = this.specInJson.paths;
for (let property in this.specInJson['x-ms-paths']) {
paths[property] = this.specInJson['x-ms-paths'][property];
}
this.specInJson.paths = paths;
}
}
resolveFileReferencesInXmsExamples() {
//Resolve all the file references for scenarios in x-ms-examples. This needs to be done before the spec is provided as an input to
//swagger-tools' validateModel().
this.specValidationResult.operations = {};
for (let apiPath in this.specInJson.paths) {
for (let verb in this.specInJson.paths[apiPath]) {
let operationId = this.specInJson.paths[apiPath][verb]['operationId'];
this.specValidationResult.operations[operationId] = {};
this.specValidationResult.operations[operationId][xmsExamples] = {};
if (this.specInJson.paths[apiPath][verb][xmsExamples]) {
//populate all the scenarios for the examples inline.
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'] = {};
for (let scenarioName in this.specInJson.paths[apiPath][verb][xmsExamples]) {
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName] = {};
if (!this.specInJson.paths[apiPath][verb][xmsExamples][scenarioName]["$ref"]) {
let msg = `$ref not found in ${this.specInJson.paths[apiPath][verb][xmsExamples][scenarioName]}`;
let e = utils.constructErrorObject(RefNotFoundError, msg);
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName]['populateScenariosInline'] = {};
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName]['populateScenariosInline'].error = e;
console.log(`${e.code} - ${e.message}`);
continue;
}
//derefence the scenarioName
let scenarioPath = path.resolve(path.dirname(this.filePath), this.specInJson.paths[apiPath][verb][xmsExamples][scenarioName]["$ref"]);
let scenarioData;
try {
scenarioData = utils.parseJSONSync(scenarioPath);
} catch (parsingError) {
let msg = `Error occured while parsing example data from "${scenarioPath}".`;
let e = utils.constructErrorObject(JsonParsingError, msg, [parsingError]);
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName]['populateScenariosInline'] = {};
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName]['populateScenariosInline'].error = e;
console.log(`${e.code} - ${e.message}`);
continue;
}
//assign the parsed example data to the scenarioName in the spec, so everything is inline.
this.specInJson.paths[apiPath][verb][xmsExamples][scenarioName] = scenarioData;
}
} else {
let msg = `Operation - "${this.specInJson.paths[apiPath][verb].operationId}", does not contain the extension: "x-ms-examples".`;
let e = utils.constructErrorObject(XmsExampleNotFoundError, msg);
this.specValidationResult.operations[operationId][xmsExamples].error = e;
console.log(`\t> ${e.code} - ${e.message}`);
}
}
}
}
initialize(options, callback) {
let self = this;
if (!callback && typeof options === 'function') {
callback = options;
options = {skipInitialization: false};
}
if (options) {
if (!options.skipInitialization) {
try {
self.specInJson = utils.parseJSONSync(self.filePath);
} catch (err) {
let msg = `Error occured while parsing the spec ${self.filePath}. \t${err.message}.`;
err.message = msg;
err.code = JsonParsingError;
self.specValidationResult.parseJsonSpec = {};
self.specValidationResult.parseJsonSpec.error = err;
console.log(`${err.code} - ${err.message}`);
return callback(err);
}
self.unifyXmsPaths();
self.resolveFileReferencesInXmsExamples();
process.chdir(path.dirname(self.filePath));
RefParser.resolve(self.specInJson, function (err, result) {
process.chdir(cwd);
if (err) {
let msg = `Error occurred in resolving the spec "${selg.filePath}". \t${err.message}.`;
err.code = 'RESOLVE_SPEC_ERROR';
err.message = msg;
self.specValidationResult.resolveSpec = {};
self.specValidationResult.resolveSpec.error = err;
console.log(`${err.code} - ${err.message}`);
return callback(err);
}
self.refSpecInJson = result;
return callback(null);
});
} else {
return callback(null);
}
}
}
//wrapper to validateModel of swagger-tools
validateModel(modelReference, data) {
let self = this;
if (!modelReference) {
throw new Error('modelReference cannot be null or undefined. It must be of a string. Example: "#/definitions/foo".');
}
if (!data) {
throw new Error('data cannot be null or undefined. It must be of a JSON object or a JSON array.');
}
return function (callback) {
swt.validateModel(self.specInJson, modelReference, data, callback);
}
}
validateDateTypes(schema, value) {
if (value !== null && value !== undefined) {
if (schema.format.match(/^date$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
throw new Error(`${schema.name} must be an instanceof Date or a string in ISO8601 format.`);
}
} else if (schema.format.match(/^date-time$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
throw new Error(`${schema.name} must be an instanceof Date or a string in ISO8601 format.`);
}
} else if (schema.format.match(/^date-time-rfc-1123$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
throw new Error(`${schema.name} must be an instanceof Date or a string in RFC-1123 format.`);
}
} else if (schema.format.match(/^unixtime$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
throw new Error(`${schema.name} must be an instanceof Date or a string in RFC-1123/ISO8601 format ` +
`for it to be serialized in UnixTime/Epoch format.`);
}
} else if (schema.format.match(/^(duration|timespan)$/ig) !== null) {
if (!moment.isDuration(value)) {
throw new Error(`${schema.name} must be a TimeSpan/Duration.`);
}
}
}
}
validateBufferType(schema, value) {
if (value !== null && value !== undefined) {
if (!Buffer.isBuffer(value)) {
throw new Error(`${schema.name} must be of type Buffer.`);
}
}
}
isValidUuid(uuid) {
let validUuidRegex = new RegExp('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$', 'ig');
return validUuidRegex.test(uuid);
};
validateBasicTypes(schema, value) {
if (value !== null && value !== undefined) {
if (schema.type.match(/^(number|integer)$/ig) !== null) {
if (typeof value !== 'number') {
throw new Error(`${schema.name} with value ${value} must be of type number.`);
}
} else if (schema.type.match(/^string$/ig) !== null) {
if (typeof value.valueOf() !== 'string') {
throw new Error(`${schema.name} with value '${value}' must be of type string.`);
}
if (schema.format && schema.format.match(/^uuid$/ig) !== null) {
if (!(typeof value.valueOf() === 'string' && this.isValidUuid(value))) {
throw new Error(`${schema.name} with value '${value}' must be of type string and a valid uuid.`);
}
}
} else if (schema.type.match(/^boolean$/ig) !== null) {
if (typeof value !== 'boolean') {
throw new Error(`${schema.name} with value ${value} must be of type boolean.`);
}
}
}
}
//entry point for type validations
validateType(schema, data) {
if (schema.type.match(/^(number|string|boolean|integer)$/ig) !== null) {
if (schema.format) {
if (schema.format.match(/^(date|date-time|timespan|duration|date-time-rfc1123|unixtime)$/ig) !== null) {
this.validateDateTypes(schema, data);
} else if (schema.format.match(/^b(yte|ase64url)$/ig) !== null) {
this.validateBufferType(schema, data);
}
} else {
this.validateBasicTypes(schema, data);
}
} else {
throw new Error(`Unknown type: "${schema.type}" provided for parameter: "${schema.name}".`);
}
};
//validates constraints
validateConstraints(schema, value, objectName) {
let constraintErrors = [];
constraints.forEach(function (constraintType) {
if (schema[constraintType] !== null && schema[constraintType] !== undefined) {
if (constraintType.match(/^maximum$/ig) !== null) {
if (schema['exclusiveMaximum']) {
if (value >= schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint ` +
`'exclusiveMaximum': true and 'maximum': ${schema[constraintType]}.`;
let e = utils.constructErrorObject('EXCLUSIVE_MAXIMUM_FAILURE', msg);
constraintErrors.push(e);
}
} else {
if (value > schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maximum': ${schema[constraintType]}.`;
let e = utils.constructErrorObject('MAXIMUM_FAILURE', msg);
constraintErrors.push(e);
}
}
} else if (constraintType.match(/^minimum$/ig) !== null) {
if (schema['exclusiveMinimum']) {
if (value <= schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint ` +
`'exclusiveMinimum': true and 'minimum': ${schema[constraintType]}.`;
e.code = 'MINIMUM_FAILURE';
let e = utils.constructErrorObject('MINIMUM_FAILURE', msg);
constraintErrors.push(e);
}
} else {
if (value < schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'minimum': ${schema[constraintType]}.`;
e.code = 'MINIMUM_FAILURE';
let e = utils.constructErrorObject('MINIMUM_FAILURE', msg);
constraintErrors.push(e);
}
}
} else if (constraintType.match(/^maxItems$/ig) !== null) {
if (value.length > schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maxItems': ${schema[constraintType]}.`;
e.code = 'MAXITEMS_FAILURE';
let e = utils.constructErrorObject('MAXITEMS_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^maxLength$/ig) !== null) {
if (value.length > schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maxLength': ${schema[constraintType]}.`;
e.code = 'MAXLENGTH_FAILURE';
let e = utils.constructErrorObject('MAXLENGTH_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^minItems$/ig) !== null) {
if (value.length < schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'minItems': ${schema[constraintType]}.`;
e.code = 'MINITEMS_FAILURE';
let e = utils.constructErrorObject('MINITEMS_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^minLength$/ig) !== null) {
if (value.length < schema[constraintType]) {
throw `'${objectName}' with value '${value}' should satify the constraint 'minLength': ${schema[constraintType]}.`;
e.code = 'MINLENGTH_FAILURE';
let e = utils.constructErrorObject('MINLENGTH_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^multipleOf$/ig) !== null) {
if (value.length % schema[constraintType] !== 0) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'multipleOf': ${schema[constraintType]}.`;
let e = utils.constructErrorObject('MULTIPLEOF_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^pattern$/ig) !== null) {
if (value.match(schema[constraintType].split('/').join('\/')) === null) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'pattern': ${schema[constraintType]}.`;
let e = utils.constructErrorObject('REGEX_PATTERN_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^uniqueItems/ig) !== null) {
if (schema[constraintType]) {
if (value.length !== value.filter(function (item, i, ar) { { return ar.indexOf(item) === i; } }).length) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'uniqueItems': ${schema[constraintType]}.`;
let e = utils.constructErrorObject('UNIQUEITEMS_FAILURE', msg);
constraintErrors.push(e);
}
}
} else if (constraintType.match(/^enum/ig) !== null) {
let isPresent = schema[constraintType].some(function (item) {
return item === value;
});
if (!isPresent && schema['x-ms-enum'] && !Boolean(schema['x-ms-enum']['modelAsString'])) {
let msg = `${value} is not a valid value for ${objectName}. The valid values are: ${JSON.stringify(schema[constraintType])}.`;
let e = utils.constructErrorObject('ENUM_FAILURE', msg);
constraintErrors.push(e);
}
}
}
});
if (constraintErrors.length > 0) {
return constraintErrors;
}
}
validateNonBodyParameter(schema, data, operationId) {
let result = {
validationErrors: null,
typeError: null
};
//constraint validation
result.validationErrors = this.validateConstraints(schema, data, schema.name);
//type validation
try {
this.validateType(schema, data);
} catch (typeError) {
result.typeError = `${typeError.message} in operation: "${operationId}".`;
}
return result;
}
validateDataModels(callback) {
let self = this;
let options = { skipInitialization: false };
if (self.specInJson && Object.keys(self.specInJson).length > 0) {
options.skipInitialization = true;
}
self.initialize(options, function(initializationErr, result) {
if (initializationErr) {
return callback(initializationErr);
}
utils.run(function* () {
let apiPaths = Object.keys(self.specInJson.paths);
for (let i = 0; i < apiPaths.length; i++) {
let verbs = Object.keys(self.specInJson.paths[apiPaths[i]]);
for (let j = 0; j < verbs.length; j++) {
let apiPath = apiPaths[i], verb = verbs[j];
let operation = self.specInJson.paths[apiPath][verb];
let operationId = operation.operationId;
console.log(`\t> Operation: ${operationId}`);
if (operation[xmsExamples]) {
let scenarios = Object.keys(operation[xmsExamples]);
for (let k = 0; k < scenarios.length; k++) {
let scenarioName = scenarios[k], scenarioData = operation[xmsExamples][scenarioName];
//validate parameters
console.log(`\t\t> ${xmsExamples}`);
console.log(`\t\t\t> Scenario: ${scenarioName}`);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters = {};
for (let l = 0; l < operation.parameters.length; l++) {
let parameter = operation.parameters[l];
let dereferencedParameter = parameter;
if (parameter['$ref']) {
dereferencedParameter = self.refSpecInJson.get(parameter['$ref']);
}
let parameterName = dereferencedParameter.name;
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName] = {};
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = true;
//check for requiredness
if (dereferencedParameter.required && !scenarioData.parameters[parameterName]) {
let msg = `Swagger spec has a parameter named "${dereferencedParameter.name}" as required for operation "${operationId}", ` +
`however this parameter is not defined in the example for scenario "${scenarioName}".`;
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
let reqError = utils.constructErrorObject(RequiredParameterNotInExampleError, msg);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = reqError;
console.error(`\t\t\t\t> ${reqError.code}: ${reqError.message}`);
continue;
}
if (dereferencedParameter.in === 'body') {
//TODO: Handle inline model definition
let modelReference = dereferencedParameter.schema['$ref'];
try {
let result = yield self.validateModel(modelReference, scenarioData.parameters[parameterName]);
if (result) {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
let msg = `Found errors in validating the body parameter for example "${scenarioName}" in operation "${operationId}".`;
let bodyValidationError = utils.constructErrorObject(BodyParameterValidationError, msg, result.errors);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = bodyValidationError;
console.error(`\t\t\t\t> ${bodyValidationError.code}: ${bodyValidationError.message}`);
} else {
let msg = `The body parameter for example "${scenarioName}" in operation "${operationId}" is valid.`;
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['result'] = msg;
console.log(`\t\t\t\t> ${BodyParameterValid}: ${msg}`);
}
} catch(err) {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
let msg = `An internal error occured while validating the body parameter for example "${scenarioName}" in operation "${operationId}".`;
let e = utils.constructErrorObject(InternalError, msg, [err]);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = e;
console.error(`\t\t\t\t> ${e.code}: ${e.message}`);
}
} else {
let errors = self.validateNonBodyParameter(dereferencedParameter, scenarioData.parameters[parameterName], operationId);
let nonBodyError;
if (errors.validationErrors) {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
let msg = `${parameterName} in operation ${operationId} failed validation constraints.`;
nonBodyError = utils.constructErrorObject(ConstraintValidationError, msg, errors.validationErrors);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = nonBodyError;
console.log(`\t\t\t\t> ${nonBodyError.code}: ${nonBodyError.message}`);
}
if (errors.typeError) {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
errors.typeError.code = TypeValidationError;
if (errors.validationErrors) {
nonBodyError.code += ' + ' + errors.typeError.code
nonBodyError.innerErrors.push(errors.typeError);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = nonBodyError;
console.log(`\t\t\t\t> ${nonBodyError.code}: ${nonBodyError.message}`);
} else {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = errors.typeError;
console.log(`\t\t\t\t> ${errors.typeError.code}: ${errors.typeError.message}`);
}
}
}
}
//validate responses
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses = {};
let res = Object.keys(operation.responses);
for (let m = 0; m < res.length; m++) {
let statusCode = res[m];
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode] = {};
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = true;
if (!scenarioData.responses[statusCode]) {
let msg = `Response with statusCode "${statusCode}" for operation "${operationId}" is present in the spec, but not in the example "${scenarioName}".`;
let e = utils.constructErrorObject(StatuscodeNotInExampleError, msg);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = e;
continue;
}
//TODO: Currently only validating $ref in responses. What if it is an inline schema? Will have to add the logic to
//add the schema in the definitions section and replace the inline schema with a ref corresponding to that definition.
if (operation.responses[statusCode]['schema'] && operation.responses[statusCode]['schema']['$ref']) {
let modelReference = operation.responses[statusCode]['schema']['$ref'];
try {
let result = yield self.validateModel(modelReference, scenarioData.responses[statusCode].body);
if (result) {
let msg = `Found errors in validating the response with statusCode "${statusCode}" for ` +
`example "${scenarioName}" in operation "${operationId}".`;
let responseBodyValidationError = utils.constructErrorObject(ResponseBodyValidationError, msg, result.errors);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = responseBodyValidationError;
console.error(`\t\t\t\t> ${responseBodyValidationError.code}: ${responseBodyValidationError.message}`);
} else {
let msg = `Response with statusCode "${statusCode}" for example "${scenarioName}" in operation "${operationId}" is valid.`
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['result'] = msg;
//console.log(`\t\t\t\t> RESPONSE_${statusCode}_VALID: ${msg}`);
}
} catch(err) {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
err.code = InternalError + (err.code ? ' ' + err.code : '');
let msg = `An internal error occured while validating the response with statusCode "${statusCode}" for ` +
`example "${scenarioName}" in operation "${operationId}".`;
let e = utils.constructErrorObject(InternalError, msg, [err]);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = e;
console.log(`\t\t\t\t> ${e.code}: ${e.message}`);
}
}
}
}
}
//validate examples present in the spec if present
//let's validate the example for body parameter if it is present
let parameters = operation.parameters;
let exampleDisplay = false;
if (parameters) {
for (let p = 0; p < parameters.length; p++) {
let param = parameters[p];
if (param && param.in && param.in === 'body') {
//TODO: Need to handle inline parameters
if (param.schema && param.schema['$ref']) {
let paramRef = param.schema['$ref'];
let dereferencedParam = self.refSpecInJson.get(paramRef);
if (dereferencedParam.example) {
console.log(`\t\t> ${exampleInSpec}`);
exampleDisplay = true;
self.specValidationResult.operations[operationId][exampleInSpec] = {};
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'] = {};
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name] = {};
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = true;
try {
let bodyParamExampleResult = yield self.validateModel(paramRef, dereferencedParam.example);
if (bodyParamExampleResult) {
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = false;
let msg = `Found errors in validating the example provided for body parameter ` +
`${param.name} with reference ${paramRef} in operation "${operationId}".`;
let bodyValidationError = utils.constructErrorObject(BodyParameterValidationError, msg, bodyParamExampleResult.errors);
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['error'] = bodyValidationError;
console.error(`\t\t\t> ${bodyValidationError.code}: ${bodyValidationError.message}`);
} else {
let msg = `The example for body parameter ${param.name} with reference ${paramRef} in operation "${operationId}" is valid.`;
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['result'] = msg;
console.log(`\t\t\t> ${BodyParameterValid}: ${msg}`);
}
} catch(err) {
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = false;
err.code = InternalError + (err.code ? ' ' + err.code : '');
let msg = `An internal error occured while validating the example provided for body parameter ` +
`${param.name} with reference ${paramRef} in operation "${operationId}".`;
let e = utils.constructErrorObject(InternalError, msg, [err]);
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name].error = e;
console.error(`\t\t\t> ${e.code}: ${e.message}`);
}
}
}
}
}
}
//let's validate the examples for response status codes if present
let responses = operation.responses;
let statusCodes = Object.keys(responses);
if (statusCodes) {
for(let q = 0; q < statusCodes.length; q++) {
if (responses[statusCodes[q]].examples) {
if (!exampleDisplay) {
console.log(`\t\t> ${exampleInSpec}`);
if (!self.specValidationResult.operations) {
self.specValidationResult.operations = {};
self.specValidationResult.operations[operationId] = {};
}
self.specValidationResult.operations[operationId][exampleInSpec] = {};
}
let responseExamples = responses[statusCodes[q]].examples;
//TODO: Handle the case for inline schema definitions.
let responseRef;
if (responses[statusCodes[q]].schema && responses[statusCodes[q]].schema['$ref']) {
responseRef = responses[statusCodes[q]].schema['$ref'];
}
let mimeTypes = Object.keys(responseExamples);
for (let r = 0; r < mimeTypes.length; r++) {
let responseData = responseExamples[mimeTypes[r]];
self.specValidationResult.operations[operationId][exampleInSpec]['responses'] = {};
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]] = {};
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = true;
try {
let responseResult = yield self.validateModel(responseRef, responseData);
if (responseResult) {
let msg = `Found errors in validating the example for response with statusCode "${statusCodes[q]}" in operation "${operationId}".`;
let responseBodyValidationError = utils.constructErrorObject(ResponseBodyValidationError, msg, responseResult.errors);
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = false;
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]].error = responseBodyValidationError;
console.log(`\t\t\t> ${responseBodyValidationError.code}: ${responseBodyValidationError.message}`);
} else {
let msg = `The example for response with statusCode "${statusCodes[q]}" in operation "${operationId}" is valid.`
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['result'] = msg;
console.log(`\t\t\t> RESPONSE_${statusCodes[q]}_VALID: ${msg}`);
}
} catch(err) {
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = false;
let msg = `An internal error occured while validating the example for response with statusCode "${statusCodes[q]}" in operation "${operationId}".`;
let e = utils.constructErrorObject(InternalError, msg, [err]);
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]].error = e;
console.log(`\t\t\t> ${e.code}: ${e.message}`);
}
}
}
}
}
}
}
return callback(null);
});
});
}
validateSpec(callback) {
let self = this;
let options = { skipInitialization: false };
if (self.specInJson && Object.keys(self.specInJson).length > 0) {
options.skipInitialization = true;
}
self.initialize(options, function(initializationError, initializationResult) {
if (initializationError) {
return callback(initializationError);
}
swt.validate(self.specInJson, function (err, result) {
if (err) {
return callback(err);
}
if (result && result.errors.length > 0) {
console.log('');
console.log('Errors');
console.log('------');
result.errors.forEach(function (err) {
if (err.path[0] === 'paths') {
err.path.shift();
console.log(`${err.code} : ${err.path.join('/')} : ${err.message}`);
} else {
console.log(`${err.code} : #/${err.path.join('/')} : ${err.message}`);
}
});
return callback(util.inspect(result.errors, {depth: null}));
} else {
return callback(null);
}
});
});
}
}
module.exports = SpecValidator;

586
lib/specValidatorTemp.js Normal file
Просмотреть файл

@ -0,0 +1,586 @@
// 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 fs = require('fs'),
path = require('path'),
util = require('util'),
deasync = require('deasync'),
swt = require('swagger-tools').specs.v2,
RefParser = require('json-schema-ref-parser'),
utils = require('./util/utils'),
constants = require('./util/constants'),
cwd = process.cwd();
var validateModel = deasync(swt.validateModel);
class SpecValidator {
constructor(specPath) {
if (!specPath && typeof specPath.valueOf() !== 'string') {
throw new Error('specPath (A file or a rawgithub url) cannot be null or undefined and must of type string');
}
this.specPath = specPath;
this.specInJson = null;
this.refSpecInJson = null;
this.specValidationResult = {};
this.initialize();
}
//wrapper to validateModel of swagger-tools
bundleSpec() {
let self = this;
return function (callback) {
RefParser.bundle(self.specPath, callback);
}
}
//wrapper to validateModel of swagger-tools
resolveSpec(specPath, data) {
let self = this;
return function (callback) {
RefParser.validateModel(self.specPath, callback);
}
}
initialize() {
let self = this;
utils.run(function* () {
try {
self.specInJson = yield self.bundleSpec();
self.refSpecInJson = yield self.resolveSpec();
} catch (err) {
throw err;
}
});
}
initialize1() {
let self = this;
try {
let resolveDone = false;
let bundleDone = false;
RefParser.bundle(self.specPath, function (err, result) {
if (err) throw err;
self.specInJson = result;
bundleDone = true;
});
deasync.loopWhile(function () { return !bundleDone; });
RefParser.resolve(self.specPath, function (err, result) {
if (err) throw err;
self.refSpecInJson = result;
resolveDone = true;
});
deasync.loopWhile(function () { return !resolveDone; });
} catch (err) {
throw err;
}
}
unifyXmsPaths() {
//unify x-ms-paths into paths
if (this.specInJson['x-ms-paths'] && this.specInJson['x-ms-paths'] instanceof Object &&
Object.keys(this.specInJson['x-ms-paths']).length > 0) {
let paths = this.specInJson.paths;
for (let property in this.specInJson['x-ms-paths']) {
paths[property] = this.specInJson['x-ms-paths'][property];
}
this.specInJson.paths = paths;
}
}
simpleValidateModel(modelReference, model) {
let self = this;
console.log(modelReference);
let result = validateModel(self.specInJson, modelReference, model);
console.log(result);
return;
}
//wrapper to validateModel of swagger-tools
validateModelsss(modelReference, data) {
let self = this;
if (!modelReference) {
throw new Error('modelReference cannot be null or undefined. It must be of a string. Example: "#/definitions/foo".');
}
if (!data) {
throw new Error('data cannot be null or undefined. It must be of a JSON object or a JSON array.');
}
return function (callback) {
swt.validateModel(self.specInJson, modelReference, data, callback);
}
}
validateDateTypes(schema, value) {
if (value !== null && value !== undefined) {
if (schema.format.match(/^date$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
throw new Error(`${schema.name} must be an instanceof Date or a string in ISO8601 format.`);
}
} else if (schema.format.match(/^date-time$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
throw new Error(`${schema.name} must be an instanceof Date or a string in ISO8601 format.`);
}
} else if (schema.format.match(/^date-time-rfc-1123$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
throw new Error(`${schema.name} must be an instanceof Date or a string in RFC-1123 format.`);
}
} else if (schema.format.match(/^unixtime$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
throw new Error(`${schema.name} must be an instanceof Date or a string in RFC-1123/ISO8601 format ` +
`for it to be serialized in UnixTime/Epoch format.`);
}
} else if (schema.format.match(/^(duration|timespan)$/ig) !== null) {
if (!moment.isDuration(value)) {
throw new Error(`${schema.name} must be a TimeSpan/Duration.`);
}
}
}
}
validateBufferType(schema, value) {
if (value !== null && value !== undefined) {
if (!Buffer.isBuffer(value)) {
throw new Error(`${schema.name} must be of type Buffer.`);
}
}
}
isValidUuid(uuid) {
let validUuidRegex = new RegExp('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$', 'ig');
return validUuidRegex.test(uuid);
};
validateBasicTypes(schema, value) {
if (value !== null && value !== undefined) {
if (schema.type.match(/^(number|integer)$/ig) !== null) {
if (typeof value !== 'number') {
throw new Error(`${schema.name} with value ${value} must be of type number.`);
}
} else if (schema.type.match(/^string$/ig) !== null) {
if (typeof value.valueOf() !== 'string') {
throw new Error(`${schema.name} with value '${value}' must be of type string.`);
}
if (schema.format && schema.format.match(/^uuid$/ig) !== null) {
if (!(typeof value.valueOf() === 'string' && this.isValidUuid(value))) {
throw new Error(`${schema.name} with value '${value}' must be of type string and a valid uuid.`);
}
}
} else if (schema.type.match(/^boolean$/ig) !== null) {
if (typeof value !== 'boolean') {
throw new Error(`${schema.name} with value ${value} must be of type boolean.`);
}
}
}
}
//entry point for type validations
validateType(schema, data) {
if (schema.type.match(/^(number|string|boolean|integer)$/ig) !== null) {
if (schema.format) {
if (schema.format.match(/^(date|date-time|timespan|duration|date-time-rfc1123|unixtime)$/ig) !== null) {
this.validateDateTypes(schema, data);
} else if (schema.format.match(/^b(yte|ase64url)$/ig) !== null) {
this.validateBufferType(schema, data);
}
} else {
this.validateBasicTypes(schema, data);
}
} else {
throw new Error(`Unknown type: "${schema.type}" provided for parameter: "${schema.name}".`);
}
};
//validates constraints
validateConstraints(schema, value, objectName) {
let constraintErrors = [];
constraints.forEach(function (constraintType) {
if (schema[constraintType] !== null && schema[constraintType] !== undefined) {
if (constraintType.match(/^maximum$/ig) !== null) {
if (schema['exclusiveMaximum']) {
if (value >= schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint ` +
`'exclusiveMaximum': true and 'maximum': ${schema[constraintType]}.`;
let e = utils.constructErrorObject('EXCLUSIVE_MAXIMUM_FAILURE', msg);
constraintErrors.push(e);
}
} else {
if (value > schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maximum': ${schema[constraintType]}.`;
let e = utils.constructErrorObject('MAXIMUM_FAILURE', msg);
constraintErrors.push(e);
}
}
} else if (constraintType.match(/^minimum$/ig) !== null) {
if (schema['exclusiveMinimum']) {
if (value <= schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint ` +
`'exclusiveMinimum': true and 'minimum': ${schema[constraintType]}.`;
e.code = 'MINIMUM_FAILURE';
let e = utils.constructErrorObject('MINIMUM_FAILURE', msg);
constraintErrors.push(e);
}
} else {
if (value < schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'minimum': ${schema[constraintType]}.`;
e.code = 'MINIMUM_FAILURE';
let e = utils.constructErrorObject('MINIMUM_FAILURE', msg);
constraintErrors.push(e);
}
}
} else if (constraintType.match(/^maxItems$/ig) !== null) {
if (value.length > schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maxItems': ${schema[constraintType]}.`;
e.code = 'MAXITEMS_FAILURE';
let e = utils.constructErrorObject('MAXITEMS_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^maxLength$/ig) !== null) {
if (value.length > schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maxLength': ${schema[constraintType]}.`;
e.code = 'MAXLENGTH_FAILURE';
let e = utils.constructErrorObject('MAXLENGTH_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^minItems$/ig) !== null) {
if (value.length < schema[constraintType]) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'minItems': ${schema[constraintType]}.`;
e.code = 'MINITEMS_FAILURE';
let e = utils.constructErrorObject('MINITEMS_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^minLength$/ig) !== null) {
if (value.length < schema[constraintType]) {
throw `'${objectName}' with value '${value}' should satify the constraint 'minLength': ${schema[constraintType]}.`;
e.code = 'MINLENGTH_FAILURE';
let e = utils.constructErrorObject('MINLENGTH_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^multipleOf$/ig) !== null) {
if (value.length % schema[constraintType] !== 0) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'multipleOf': ${schema[constraintType]}.`;
let e = utils.constructErrorObject('MULTIPLEOF_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^pattern$/ig) !== null) {
if (value.match(schema[constraintType].split('/').join('\/')) === null) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'pattern': ${schema[constraintType]}.`;
let e = utils.constructErrorObject('REGEX_PATTERN_FAILURE', msg);
constraintErrors.push(e);
}
} else if (constraintType.match(/^uniqueItems/ig) !== null) {
if (schema[constraintType]) {
if (value.length !== value.filter(function (item, i, ar) { { return ar.indexOf(item) === i; } }).length) {
let msg = `'${objectName}' with value '${value}' should satify the constraint 'uniqueItems': ${schema[constraintType]}.`;
let e = utils.constructErrorObject('UNIQUEITEMS_FAILURE', msg);
constraintErrors.push(e);
}
}
} else if (constraintType.match(/^enum/ig) !== null) {
let isPresent = schema[constraintType].some(function (item) {
return item === value;
});
if (!isPresent && schema['x-ms-enum'] && !Boolean(schema['x-ms-enum']['modelAsString'])) {
let msg = `${value} is not a valid value for ${objectName}. The valid values are: ${JSON.stringify(schema[constraintType])}.`;
let e = utils.constructErrorObject('ENUM_FAILURE', msg);
constraintErrors.push(e);
}
}
}
});
if (constraintErrors.length > 0) {
return constraintErrors;
}
}
validateNonBodyParameter(schema, data, operationId) {
let result = {
validationErrors: null,
typeError: null
};
//constraint validation
result.validationErrors = this.validateConstraints(schema, data, schema.name);
//type validation
try {
this.validateType(schema, data);
} catch (typeError) {
result.typeError = `${typeError.message} in operation: "${operationId}".`;
}
return result;
}
validateScenario(apiPath, verb, scenarioName) {
let self = this;
let operation = specInJson.paths[apiPath][verb];
let operationId = operation.operationId;
let scenarioData = operation[xmsExamples][scenarioName];
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters = {};
for (let l = 0; l < operation.parameters.length; l++) {
let parameter = operation.parameters[l];
let dereferencedParameter = parameter;
if (parameter['$ref']) {
dereferencedParameter = self.refSpecInJson.get(parameter['$ref']);
}
let parameterName = dereferencedParameter.name;
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName] = {};
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = true;
//check for requiredness
if (dereferencedParameter.required && !scenarioData.parameters[parameterName]) {
let msg = `Swagger spec has a parameter named "${dereferencedParameter.name}" as required for operation "${operationId}", ` +
`however this parameter is not defined in the example for scenario "${scenarioName}".`;
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
let reqError = utils.constructErrorObject(RequiredParameterNotInExampleError, msg);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = reqError;
console.error(`\t\t\t\t> ${reqError.code}: ${reqError.message}`);
continue;
}
if (dereferencedParameter.in === 'body') {
//TODO: Handle inline model definition
let modelReference = dereferencedParameter.schema['$ref'];
try {
let bodyValidationResult = null;
let bodyValidationDone = false
swt.validateModel(self.specInJson, modelReference, scenarioData.parameters[parameterName], function (err, result) {
bodyValidationResult
});
if (result) {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
let msg = `Found errors in validating the body parameter for example "${scenarioName}" in operation "${operationId}".`;
let bodyValidationError = utils.constructErrorObject(BodyParameterValidationError, msg, result.errors);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = bodyValidationError;
console.error(`\t\t\t\t> ${bodyValidationError.code}: ${bodyValidationError.message}`);
} else {
let msg = `The body parameter for example "${scenarioName}" in operation "${operationId}" is valid.`;
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['result'] = msg;
console.log(`\t\t\t\t> ${BodyParameterValid}: ${msg}`);
}
} catch(err) {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
let msg = `An internal error occured while validating the body parameter for example "${scenarioName}" in operation "${operationId}".`;
let e = utils.constructErrorObject(InternalError, msg, [err]);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = e;
console.error(`\t\t\t\t> ${e.code}: ${e.message}`);
}
} else {
let errors = self.validateNonBodyParameter(dereferencedParameter, scenarioData.parameters[parameterName], operationId);
let nonBodyError;
if (errors.validationErrors) {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
let msg = `${parameterName} in operation ${operationId} failed validation constraints.`;
nonBodyError = utils.constructErrorObject(ConstraintValidationError, msg, errors.validationErrors);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = nonBodyError;
console.log(`\t\t\t\t> ${nonBodyError.code}: ${nonBodyError.message}`);
}
if (errors.typeError) {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
errors.typeError.code = TypeValidationError;
if (errors.validationErrors) {
nonBodyError.code += ' + ' + errors.typeError.code
nonBodyError.innerErrors.push(errors.typeError);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = nonBodyError;
console.log(`\t\t\t\t> ${nonBodyError.code}: ${nonBodyError.message}`);
} else {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = errors.typeError;
console.log(`\t\t\t\t> ${errors.typeError.code}: ${errors.typeError.message}`);
}
}
}
}
//validate responses
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses = {};
let res = Object.keys(operation.responses);
for (let m = 0; m < res.length; m++) {
let statusCode = res[m];
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode] = {};
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = true;
if (!scenarioData.responses[statusCode]) {
let msg = `Response with statusCode "${statusCode}" for operation "${operationId}" is present in the spec, but not in the example "${scenarioName}".`;
let e = utils.constructErrorObject(StatuscodeNotInExampleError, msg);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = e;
continue;
}
//TODO: Currently only validating $ref in responses. What if it is an inline schema? Will have to add the logic to
//add the schema in the definitions section and replace the inline schema with a ref corresponding to that definition.
if (operation.responses[statusCode]['schema'] && operation.responses[statusCode]['schema']['$ref']) {
let modelReference = operation.responses[statusCode]['schema']['$ref'];
try {
let result = self.validateModel(modelReference, scenarioData.responses[statusCode].body);
if (result) {
let msg = `Found errors in validating the response with statusCode "${statusCode}" for ` +
`example "${scenarioName}" in operation "${operationId}".`;
let responseBodyValidationError = utils.constructErrorObject(ResponseBodyValidationError, msg, result.errors);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = responseBodyValidationError;
console.error(`\t\t\t\t> ${responseBodyValidationError.code}: ${responseBodyValidationError.message}`);
} else {
let msg = `Response with statusCode "${statusCode}" for example "${scenarioName}" in operation "${operationId}" is valid.`
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['result'] = msg;
console.log(`\t\t\t\t> RESPONSE_${statusCode}_VALID: ${msg}`);
}
} catch(err) {
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
err.code = InternalError + (err.code ? ' ' + err.code : '');
let msg = `An internal error occured while validating the response with statusCode "${statusCode}" for ` +
`example "${scenarioName}" in operation "${operationId}".`;
let e = utils.constructErrorObject(InternalError, msg, [err]);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = e;
console.log(`\t\t\t\t> ${e.code}: ${e.message}`);
}
}
}
}
validateDataModels() {
let self = this;
utils.run(function* () {
let apiPaths = Object.keys(self.specInJson.paths);
for (let i = 0; i < apiPaths.length; i++) {
let verbs = Object.keys(self.specInJson.paths[apiPaths[i]]);
for (let j = 0; j < verbs.length; j++) {
let apiPath = apiPaths[i], verb = verbs[j];
let operation = self.specInJson.paths[apiPath][verb];
let operationId = operation.operationId;
console.log(`\t> Operation: ${operationId}`);
if (operation[xmsExamples]) {
let scenarios = Object.keys(operation[xmsExamples]);
for (let k = 0; k < scenarios.length; k++) {
let scenarioName = scenarios[k], scenarioData = operation[xmsExamples][scenarioName];
//validate parameters
console.log(`\t\t> ${xmsExamples}`);
console.log(`\t\t\t> Scenario: ${scenarioName}`);
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters = {};
self.validateScenario(apiPath, verb, scenarioName)
}
//validate examples present in the spec if present
//let's validate the example for body parameter if it is present
let parameters = operation.parameters;
let exampleDisplay = false;
if (parameters) {
for (let p = 0; p < parameters.length; p++) {
let param = parameters[p];
if (param && param.in && param.in === 'body') {
//TODO: Need to handle inline parameters
if (param.schema && param.schema['$ref']) {
let paramRef = param.schema['$ref'];
let dereferencedParam = self.refSpecInJson.get(paramRef);
if (dereferencedParam.example) {
console.log(`\t\t> ${exampleInSpec}`);
exampleDisplay = true;
self.specValidationResult.operations[operationId][exampleInSpec] = {};
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'] = {};
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name] = {};
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = true;
try {
let bodyParamExampleResult = yield self.validateModel(paramRef, dereferencedParam.example);
if (bodyParamExampleResult) {
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = false;
let msg = `Found errors in validating the example provided for body parameter ` +
`${param.name} with reference ${paramRef} in operation "${operationId}".`;
let bodyValidationError = utils.constructErrorObject(BodyParameterValidationError, msg, bodyParamExampleResult.errors);
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['error'] = bodyValidationError;
console.error(`\t\t\t> ${bodyValidationError.code}: ${bodyValidationError.message}`);
} else {
let msg = `The example for body parameter ${param.name} with reference ${paramRef} in operation "${operationId}" is valid.`;
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['result'] = msg;
console.log(`\t\t\t> ${BodyParameterValid}: ${msg}`);
}
} catch(err) {
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = false;
err.code = InternalError + (err.code ? ' ' + err.code : '');
let msg = `An internal error occured while validating the example provided for body parameter ` +
`${param.name} with reference ${paramRef} in operation "${operationId}".`;
let e = utils.constructErrorObject(InternalError, msg, [err]);
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name].error = e;
console.error(`\t\t\t> ${e.code}: ${e.message}`);
}
}
}
}
}
}
//let's validate the examples for response status codes if present
let responses = operation.responses;
let statusCodes = Object.keys(responses);
if (statusCodes) {
for(let q = 0; q < statusCodes.length; q++) {
if (responses[statusCodes[q]].examples) {
if (!exampleDisplay) {
console.log(`\t\t> ${exampleInSpec}`);
if (!self.specValidationResult.operations) {
self.specValidationResult.operations = {};
self.specValidationResult.operations[operationId] = {};
}
self.specValidationResult.operations[operationId][exampleInSpec] = {};
}
let responseExamples = responses[statusCodes[q]].examples;
//TODO: Handle the case for inline schema definitions.
let responseRef;
if (responses[statusCodes[q]].schema && responses[statusCodes[q]].schema['$ref']) {
responseRef = responses[statusCodes[q]].schema['$ref'];
}
let mimeTypes = Object.keys(responseExamples);
for (let r = 0; r < mimeTypes.length; r++) {
let responseData = responseExamples[mimeTypes[r]];
self.specValidationResult.operations[operationId][exampleInSpec]['responses'] = {};
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]] = {};
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = true;
try {
let responseResult = yield self.validateModel(responseRef, responseData);
if (responseResult) {
let msg = `Found errors in validating the example for response with statusCode "${statusCodes[q]}" in operation "${operationId}".`;
let responseBodyValidationError = utils.constructErrorObject(ResponseBodyValidationError, msg, responseResult.errors);
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = false;
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]].error = responseBodyValidationError;
console.log(`\t\t\t> ${responseBodyValidationError.code}: ${responseBodyValidationError.message}`);
} else {
let msg = `The example for response with statusCode "${statusCodes[q]}" in operation "${operationId}" is valid.`
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['result'] = msg;
console.log(`\t\t\t> RESPONSE_${statusCodes[q]}_VALID: ${msg}`);
}
} catch(err) {
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = false;
let msg = `An internal error occured while validating the example for response with statusCode "${statusCodes[q]}" in operation "${operationId}".`;
let e = utils.constructErrorObject(InternalError, msg, [err]);
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]].error = e;
console.log(`\t\t\t> ${e.code}: ${e.message}`);
}
}
}
}
}
}
}
}
return callback(null);
});
}
validateSpec() {
let self = this;
let validationResult;
try {
let done = false;
swt.validate(self.specInJson, function (err, result) {
if (err) { throw err; }
validationResult = result;
done = true;
});
deasync.loopWhile(function () { return !done; });
} catch (err) {
throw err;
}
console.dir(validationResult, { depth: null, colors: true });
return validationResult;
}
}
module.exports = SpecValidator;

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

@ -31,6 +31,14 @@ var Constants = {
ConstraintValidationError: 'CONSTRAINT_VALIDATION_ERROR',
StatuscodeNotInExampleError: 'STATUS_CODE_NOT_IN_EXAMPLE_ERROR',
SemanticValidationError: 'SEMANTIC_VALIDATION_ERROR'
},
EnvironmentVariables: {
ClientId: 'CLIENT_ID',
Domain: 'DOMAIN',
ApplicationSecret: 'APPLICATION_SECRET',
AzureSubscriptionId: 'AZURE_SUBSCRIPTION_ID',
AzureLocation: 'AZURE_LOCATION',
AzureResourcegroup: 'AZURE_RESOURCE_GROUP'
}
};

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

@ -5,6 +5,7 @@
var fs = require('fs'),
util = require('util'),
path = require('path'),
jsonPointer = require('json-pointer'),
log = require('./logging'),
request = require('request');
@ -37,7 +38,7 @@ exports.stripBOM = function stripBOM(content) {
* @param {string} specPath - A local file path or a (github) url to the swagger spec.
* The method will auto convert a github url to rawgithub url.
*
* @returns {object} Swagger - Parsed Swagger document in JSON format.
* @returns {object} jsonDoc - Parsed document in JSON format.
*/
exports.parseJson = function parseJson(specPath) {
let result = null;
@ -238,20 +239,70 @@ exports.parseReferenceInSwagger = function parseReferenceInSwagger(reference) {
* @return {string} resolved path
*/
exports.joinPath = function joinPath() {
let finalPath = '';
for (let arg in arguments) {
finalPath = path.join(finalPath, arguments[arg]);
}
let finalPath = path.join.apply(path, arguments);
finalPath = finalPath.replace(/\\/gi, '/');
finalPath = finalPath.replace(/^(http|https):\/(.*)/gi, '$1://$2');
log.silly(`The final path is: ${finalPath}.`);
return finalPath;
};
/*
* Provides a parsed JSON from the given file path or a url. Same as exports.parseJson(). However,
* this method accepts variable number of path segments as strings and joins them together.
* After joining the path, it internally calls exports.parseJson().
*
* @param variable number of arguments and all the arguments must be of type string.
*
* @returns {object} jsonDoc - Parsed document in JSON format.
*/
exports.parseJsonWithPathFragments = function parseJsonWithPathFragments() {
let specPath = exports.joinPath.apply(this, arguments);
return exports.parseJson(specPath);
};
exports.merge = function merge(obj, src) {
Object.keys(src).forEach(function(key) { obj[key] = src[key]; });
return obj;
/*
* Merges source object into the target object
* @param {object} source The object that needs to be merged
*
* @param {object} target The object to be merged into
*
* @returns {object} target - Returns the merged target object.
*/
exports.mergeObjects = function mergeObjects(source, target) {
Object.keys(source).forEach(function(key) {
target[key] = source[key];
});
return target;
}
exports.getObject = function getObject(doc, ptr) {
let result;
try {
result = jsonPointer.get(doc, ptr);
} catch (err) {
console.log(err);
}
return result;
};
exports.setObject = function setObject(doc, ptr, value) {
let result;
try {
result = jsonPointer.set(doc, ptr, value);
} catch (err) {
console.log(err);
}
return result;
};
exports.removeObject = function removeObject(doc, ptr) {
let result;
try {
result = jsonPointer.remove(doc, ptr);
} catch (err) {
console.log(err);
}
return result;
};
exports = module.exports;

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

@ -11,6 +11,7 @@
"license": "MIT",
"dependencies": {
"azure-arm-resource": "^1.6.1-preview",
"json-pointer": "^0.6.0",
"moment": "^2.14.1",
"ms-rest": "^1.15.2",
"ms-rest-azure": "^1.15.2",

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

@ -122,104 +122,4 @@ exports.logDetailedInfo = function logDetailedInfo(validator, json) {
}
};
exports.sanitizeParameters = function sanitizeParameters(exampleParameters) {
if (exampleParameters) {
if (exampleParameters.subscriptionId) {
exampleParameters.subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
}
if (exampleParameters.resourceGroupName) {
exampleParameters.resourceGroupName = process.env['AZURE_RESOURCE_GROUP'];
}
}
return exampleParameters;
};
exports.liveTest = function liveTest(specPath, operationId) {
exports.validateEnvironmentVariables();
log.transports.console.level = 'info';
let clientId = process.env['CLIENT_ID'];
let domain = process.env['DOMAIN'];
let secret = process.env['APPLICATION_SECRET'];
let subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
let location = process.env['AZURE_LOCATION'] || 'westus';
let resourceGroupName = process.env['AZURE_RESOURCE_GROUP'] || utils.generateRandomId('testrg');
let validator = new SpecValidator(specPath);
let operation, xmsExamples;
validator.initialize().then(function() {
log.info(`Running live test using x-ms-examples in ${operationId} in ${specPath}:\n`);
operation = validator.getOperationById(operationId);
xmsExamples = operation[Constants.xmsExamples];
if (xmsExamples) {
for (let scenario in xmsExamples) {
let xmsExample = xmsExamples[scenario];
let parameters = exports.sanitizeParameters(xmsExample.parameters);
let result = validator.validateRequest(operation, xmsExample.parameters);
if (result.validationResult && result.validationResult.errors && result.validationResult.errors.length) {
let msg = `Cannot proceed ahead with the live test for operation: ${operationId}. Found validation errors in the request.\n` +
`${util.inspect(result.validationResult.errors, {depth: null})}`;
throw new Error(msg);
}
let req = result.request;
if (!req) {
throw new Error(`Cannot proceed ahead with the live test for operation: ${operationId}. The request object is undefined.`);
}
if (req.body !== null && req.body !== undefined) {
req.body = JSON.stringify(req.body);
}
msrestazure.loginWithServicePrincipalSecret(clientId, secret, domain, function(err, creds, subscriptions) {
if (err) {
throw err;
}
let resourceClient = new ResourceManagementClient(creds, subscriptionId);
let client = new msrestazure.AzureServiceClient(creds);
exports.createResourceGroup(resourceClient, location, resourceGroupName, function(err, resourceGroup) {
if (err) {
throw err;
}
client.sendRequest(req, function(err, result, request, response) {
log.info(request);
log.info(response);
if (err) {
throw err;
}
log.info(result);
});
});
});
}
}
}).catch(function (err) {
log.error(err);
});
};
exports.createResourceGroup = function createResourceGroup(resourceClient, location, resourceGroupName, callback) {
resourceClient.resourceGroups.get(resourceGroupName, function (err, result, request, response) {
if (err && err.statusCode === 404) {
log.info(`Creating resource group: \'${resourceGroupName}\', if not present.`);
let groupParameters = { location: location, tags: { 'live-test': 'live-test'} };
return resourceClient.resourceGroups.createOrUpdate(resourceGroupName, groupParameters, callback);
} else {
return callback(null, result);
}
});
};
exports.getResourceGroup = function getResourceGroup(resourceClient, resourceGroupName, callback) {
log.info(`Searching ResourceGroup: ${resourceGroupName}.`)
resourceClient.resourceGroups.get(resourceGroupName, callback);
}
exports.validateEnvironmentVariables = function validateEnvironmentVariables() {
var envs = [];
if (!process.env['CLIENT_ID']) envs.push('CLIENT_ID');
if (!process.env['DOMAIN']) envs.push('DOMAIN');
if (!process.env['APPLICATION_SECRET']) envs.push('APPLICATION_SECRET');
if (!process.env['AZURE_SUBSCRIPTION_ID']) envs.push('AZURE_SUBSCRIPTION_ID');
if (envs.length > 0) {
throw new Error(util.format('please set/export the following environment variables: %s', envs.toString()));
}
};
exports = module.exports;